o
    ;ήcu                     @   s   d dl mZ d dlmZmZmZmZ d dlmZ d dl	m
Z
 d dlmZmZmZ d dlmZ eeZG dd deZG d	d
 d
eZdS )    )	getLogger)DictListTupleUnion)Fusion)FusionUtils)	NodeProtoTensorProtohelper)	OnnxModelc                       s  e Zd ZdZd2dedef fddZdeded	e	eef f fd
dZ
dedeeee f dedefddZdd Zdd Zdd Zdd Zdd Zdede	eed	ef f fddZ			d3deded ed!ed"ed	ef d#efd$d%Zd&d' Zd(d) Zd*d+ Zd,d- Zd.d/ Zd0d1 Z  ZS )4FusionEmbedLayerNoMaskz
    Fuse embedding layer into one node (EmbedLayerNormalization).
    It supports the following model types: BERT, DistilBert, ALBert.
    no maskmodeldescriptionc                    sB   t  |dddg| t|| _| jji dd| _d | _d | _d S )NEmbedLayerNormalizationLayerNormalizationSkipLayerNormalizationT)update)	super__init__r   utilsr   infer_runtime_shapeshape_infer_helper	attention
embed_node)selfr   r   	__class__ Q/tmp/pip-target-vg8gfxp4/lib/python/onnxruntime/transformers/fusion_embedlayer.pyr      s   

zFusionEmbedLayerNoMask.__init__addreturnNc                 C   sP   | j |dgdg}|d u rd S | j |dgdg}|d u r d S |d |d fS )NGatherr      )r   match_parent_path)r   r!   gather_0_pathgather_1_pathr   r   r    match_two_gather$   s   z'FusionEmbedLayerNoMask.match_two_gather	layernorminput_name_to_nodesis_distil_bertc           	      C   sf  | j j|d|dd| _| jdu r|jd |vrdS ||jd  }t|dkrx|d jdkrx|d jd |v rx||d jd  }t|dkrw|d jdkrw|d jd |v rw||d jd  }|D ]}|jdkrm|| _ d	S q`td
d |D }n	tdd |D }|r|g dkr|g dkr|g dkrtd dS d	S |g dkr|g dkrtd dS d	S )a  Check that LayerNormalization has a child of Attention node or subgraph like Attention.

        Args:
            layernorm (NodeProto): LayerNormalization node
            input_name_to_nodes (Dict[str, List[NodeProto]]): map from input name to nodes
            is_distil_bert (bool): whether it is DistilBert or not

        Returns:
            bool: whether there is Attention node or subgraph like Attention
        	AttentionF)	recursiveNr   r$   MatMulAddTc                 S      g | ]}|j qS r   op_type.0childr   r   r    
<listcomp>U       zCFusionEmbedLayerNoMask.check_attention_subgraph.<locals>.<listcomp>c                 S   r0   r   r1   r3   r   r   r    r6   W   r7   )r.   r.   r.   Shaper   )r/   r.   r.   r.   r8   r8   )r/   r.   r.   r.   r8   z<No Attention like subgraph in children of LayerNormalization)r/   r.   r.   r.   )r.   r.   r.   r   )	r   find_first_child_by_typer   outputlenr2   sortedloggerdebug)	r   r)   r*   r+   childrengrandchildrennodesnodechildren_typesr   r   r    check_attention_subgraph/   sL   
,







z/FusionEmbedLayerNoMask.check_attention_subgraphc                 C   s  | j |ddgddg}|du r"| j |g dg d}|du r"dS |d |d	 }}|jd |kr4dS | j |g d
g dfg dg dfg|\}}}|du rSdS |d }	| j|	ddrg| j|	ddsidS |d }
| j|
ddswdS |d	 }|jd |krdS dS )az    Match position embedding path from input_ids to Gather for DistilBert.

        Pattern is like the following:
                 (input_ids)
                      |
                     Shape
                       |                          |    Gather (indices=1)
                       |       |
                       |      Cast (optional)
                       |       |
                       |      Range (start=0, end=*, delta=1)
                       |       |
                       |    Unsqueeze
                       |    /
                      Expand
                        |
                      Gather
        Expandr8   r$   N)rE   WhereReshaper8   )r$   r$      r   Fr   )	UnsqueezeRangeCastr#   r8   )r   r   r$   r   r   )rJ   rK   r#   r8   )r   r   r$   r   rH   T)r   r%   inputmatch_parent_pathsr   check_node_input_value)r   position_embedding_gather	input_idsoutput_name_to_nodepath1expandshape_path2
range_nodegather_node
shape_noder   r   r    #match_position_embedding_distilbertn   sD   
z:FusionEmbedLayerNoMask.match_position_embedding_distilbertc                 C   s   dS )aY  Match position embedding path from input_ids to Gather for Roberta.

        Roberta Embedding Layer Pattern (* is optional since it might be removed by ORT, ? is the padding word id):
          (input_ids) --> Equal(B=?) -- Not -- Cast(to=6) -- CumSum(axis=1) -- Mul -- Cast(to=7) -- Add(B=1) -- Cast(to=7)* --> Gather
                                                |                              ^
                                                V                              |
                                                +------------------------------+

        Roberta new pattern from transformers v4.9:
           (input_ids) --> Equal(B=?) -- Not -- Cast(to=6) -- CumSum(axis=1) -- Add(B=0) -- Mul -- Cast(to=7) -- Add(B=1) --> Gather
                                                |                                           ^
                                                V                                           |
                                                +-------------------------------------------+

        start_node = position_embedding_gather
        start_index = 1

        # match optional Cast node.
        parent = self.model.get_parent(start_node, start_index, output_name_to_node)
        if parent is None:
            return
        if parent.op_type == "Cast":
            if OnnxModel.get_node_attribute(parent, "to") != 7:
                return
            start_node = parent
            start_index = 0

        i, path, return_indices = self.model.match_parent_paths(
            start_node,
            [ (['Add', 'Cast', 'Mul', 'CumSum', 'Cast', 'Not', 'Equal'], [start_index, 0, 0, 0, 0, 0, 0]),
              (['Add', 'Cast', 'Mul', 'Add', 'CumSum', 'Cast', 'Not', 'Equal'], [start_index, 0, 0, 0, 0, 0, 0, 0])],
            output_name_to_node)

        if path is not None:
            # constant input of Add shall be 1.
            i, value = self.model.get_constant_input(path[0])
            if value != 1:
                return False

            _, self.padding_word_id = self.model.get_constant_input(path[-1])

            return input_ids == path[-1].input[0]
        Fr   r   rQ   rR   rS   r   r   r     match_position_embedding_roberta   s   -z7FusionEmbedLayerNoMask.match_position_embedding_robertac                 C   s  | j |ddgddg|}|du rdS |\}}| j |jd }|durTt|jdkrT|jd dkrT| j|ddgrT| j|ddgrTt|jd	ksV| j|d	dgsVdS | j  }|d
k rjt	
|ddgsidS n| j|ddgsudS | j |d|}	|	du rdS |	jdkr| j|	ddsdS | j |	d|}
n|	}
|
du s|
jdkrdS | j|
ddsdS | j |
d|}|du s|jdkrdS ||jd kS )a    Match position embedding path from input_ids to Gather for BERT.

        BERT Embedding Layer Pattern:       
                                    (input_ids)
                                   /                                          /          Shape
                                /              |
                              /              Gather (indices=1)
                             /                  |
                            /                  Add (optional, B=0)
                           /                    |
                        Gather (segment_ids) Unsqueeze (axes=0)
                           \        |           |
                            \     Gather      Slice (data[1,512], starts=0, ends=*, axes=1, steps=1)
                              \    /            |
                                Add          Gather 
                                   \       /
                                      Add
                                       |
                                LayerNormalization
        SlicerJ   r$   rH   NFr            axesr/   r#   r8   )r   r%   get_constant_valuerN   r;   rV   r   rP   get_opset_versionr   check_node_attribute
get_parentr2   )r   rQ   rR   rS   pathslice	unsqueezeslice_weightopset_versionrB   gatherrV   r   r   r    match_position_embedding_bert   s`   

z4FusionEmbedLayerNoMask.match_position_embedding_bertc                 C   s(   |  |||r	dS | |||rdS dS )NTF)rn   r\   r]   r   r   r    match_position_embedding#  s
   z/FusionEmbedLayerNoMask.match_position_embeddingc                 C   s  |j d }|r|j d nd}|j d }| jdura| j|}| j|}|r(|s*J t|dkr>t|dkr>|d |d ksItd|| dS |ra| j||satd|| j| dS | j	|j d }	|	du sut|	j
dkr|td dS | j	|j d }
|
du st|
j
dks|	j
d |
j
d krtd	 dS |r| j	|j d }|du st|j
dks|	j
d |j
d krtd
 dS |	j
d |
j
d krtd|j d  d|	j
d  d|j d  d|
j
d   |rB|	j
d |j
d krtd|j d  d|	j
d  d|j d  d|j
d   |
j
d |j
d krBtd|j d  d|
j
d  d|j d  d|j
d   dS )zXSanity check of embedding weights, and match hidden_size of weights and shape of inputs.r$   NrH   zfCannot fuse EmbedLayerNormalization: input_ids and position_ids not matched in 2nd dimension: {} vs {}FzaCannot fuse EmbedLayerNormalization: input_ids and segment_ids does not have same shape: {} != {}r   zICannot fuse EmbedLayerNormalization: word embedding table is not expectedzMCannot fuse EmbedLayerNormalization: position embedding table is not expectedzLCannot fuse EmbedLayerNormalization: segment embedding table is not expectedzword_embedding_table (z) size z <= position_embedding_table (z <= segment_embedding_table (zposition_embedding_table (T)rN   r   get_edge_shaper;   r=   infoformatcompare_shaper   rd   rV   warning)r   word_embedding_gathersegment_embedding_gatherrQ   rR   segment_idsposition_idsinput_ids_shapeposition_ids_shapeword_embedding_tableposition_embedding_tablesegment_embedding_tabler   r   r    check_embedding1  s   








222z&FusionEmbedLayerNoMask.check_embedding
input_namec                 C   sd   d}| j |}|dur&|jjjtjkr | j|\}}||fS |}||fS | j|\}}||fS )a  Cast a graph input or node input to int32.

        Args:
            input_name (str): name of graph input or node input

        Returns:
            A tuple of casted input name and the cast node.
            int32_output (str): If input is int32, it is the input name, Otherwise it is output name of Cast node.
            input_cast_node (Union[None, NodeProto]): Cast node. It could be None if input is int32.
        N)	r   find_graph_inputtypetensor_type	elem_typer
   INT32r   cast_input_to_int32)r   r   input_cast_nodegraph_inputint32_outputr   r   r    cast_to_int32|  s   z$FusionEmbedLayerNoMask.cast_to_int32FrR   ru   rQ   rv   rx   c                 C   s  g }|  |\}}	| jd}
|jdkr|jd }|jd }n
|jd }|jd }d}|durL|  |jd \}}	|||jd |jd |jd ||g}n|d|jd |jd d||g}|durp|d |  |\}}	|| |
d	 |
d
 g}|r||
d  tjd|||
d}d|_|j	D ]}|j
dkr|j	|g qt|j	dkr|j	tddg || |D ]	}| j| j|j
< q| j| || _|S )ag  Create an EmbedLayerNormalization node. Note that segment embedding is optional.

        Args:
            input_ids (str): input_ids for word embeddings
            layernorm (NodeProto): LayerNormalization or SkipLayerNormalization node.
            word_embedding_gather (NodeProto): the Gather node for word embedding
            position_embedding_gather (NodeProto): the Gather node for position embedding
            segment_embedding_gather (Union[None, NodeProto]): the Gather node for segment embedding, or None.

        Returns:
            NodeProto: the EmbedLayerNormalization node created.
        r   r   r$   rH   r`   Nr    _output_dummy_mask_index_embedding_sum)outputsnamezcom.microsoftepsilong-q=)r   r   create_node_namer2   rN   appendr   	make_nodedomain	attributer   extendr;   make_attributethis_graph_namenode_name_to_graph_namenodes_to_addr   )r   rR   r)   ru   rQ   rv   rx   embedding_sum_outputr   rW   	node_namegammabetaembed_node_inputsrw   embed_node_outputsr   attrB   r   r   r    create_fused_node  sj   









z(FusionEmbedLayerNoMask.create_fused_nodec                 C   s$   | j |jd |jd  d| _d S )Nr   T)r   replace_input_of_all_nodesr:   prune_graph)r   r)   r   r   r   r    finish_fusion  s   
z$FusionEmbedLayerNoMask.finish_fusionc                 C   s   | j |}t|dkS )a@  Check that Add before layer norm has an output to add before next layernorm

        Args:
            add_before_layer_norm (NodeProto): Add before any LayerNormalization node in topological order of graph

        Returns:
            bool: whether there is an extra output needed out of embed layer norm node
        r$   )r   get_childrenr;   )r   add_before_layer_normrA   r   r   r    is_embedding_sum_needed  s   
z.FusionEmbedLayerNoMask.is_embedding_sum_neededc              	   C   s   |  |}|d u rdS |jd }|\}}|jd }	|jd }
| j||dds(dS | |d |s1dS d}| |r:d}| |	|||d |
|}| j|jd |jd  |r^| j||jd  dS )NFr   r$   r+   TrH   )	r(   r:   rN   rD   r~   r   r   r   r   )r   r)   add_before_layernormr*   rS   
two_gather
add_outputru   rQ   rR   rx   optional_embedding_sum_outputr   r   r   r    	fuse_gpt2  s6   




z FusionEmbedLayerNoMask.fuse_gpt2c           
      C   s   |  |}|du rdS |\}}|jd }| j||ddsdS | |||s'dS | |d|s0dS | ||||d}	| ||	 dS )a  Fuse embedding layer for DistilBert
        Args:
            layernorm (NodeProto): node of LayerNormalization or SkipLayerNormalization
            add_before_layernorm (NodeProto): the Add node before LayerNormalization, or the SkipLayerNormalization itself
            input_name_to_nodes (Dict[str, List[NodeProto]]): map from input name to nodes
            output_name_to_node (Dict[str, List[NodeProto]]): map from output name to nodes
        NFr$   Tr   )r(   rN   rD   ro   r~   r   r   )
r   r)   r   r*   rS   r   ru   rQ   rR   r   r   r   r    fuse_distilbert>  s    


z&FusionEmbedLayerNoMask.fuse_distilbertc                 C   s   | j |dgdg}|du rdS | |d }|du rdS |\}}|jd }	| j||dds0dS | j |dgdg}
|
du r@dS |
d }| ||	|sZ| ||	|sTdS |}|}|}| |||scdS | |	||||}| || dS )	a  Fuse embedding layer for Bert
        Args:
            layernorm (NodeProto): node of LayerNormalization or SkipLayerNormalization
            add_before_layernorm (NodeProto): the Add node before LayerNormalization, or the SkipLayerNormalization itself
            input_name_to_nodes (Dict[str, List[NodeProto]]): map from input name to nodes
            output_name_to_node (Dict[str, List[NodeProto]]): map from output name to nodes
        r/   r   NFr$   r   r#   T)	r   r%   r(   rN   rD   ro   r~   r   r   )r   r)   r   r*   rS   add_2_gatherr   ru   rv   rR   position_embedding_pathrQ   tempr   r   r   r    	fuse_bertg  s>   	
z FusionEmbedLayerNoMask.fuse_bertc                 C   sx   |j dkr| j|dgdg}|d u rd S |d }n|}| ||||r&d S | ||||r0d S | ||||r:d S d S )Nr   r/   r   )r2   r   r%   r   r   r   )r   rB   r*   rS   first_add_pathr   r   r   r    fuse  s   

zFusionEmbedLayerNoMask.fuse)r   )NF)__name__
__module____qualname____doc__r   strr   r	   r   r   r(   r   r   boolrD   r\   r^   rn   ro   r~   r   r   r   r   r   r   r   r   __classcell__r   r   r   r    r      sP    "
?>/H"K

`8)2r   c                       s.   e Zd Zdef fddZ fddZ  ZS )FusionEmbedLayerNormalizationr   c                    s   t  |d d S )Nz	with mask)r   r   )r   r   r   r   r    r     s   z&FusionEmbedLayerNormalization.__init__c                    s   d | _ d | _t ||| | j rC| jrE| j jd }||v rG|| }|jdkrI| j}|jd }| j|g |j| ||j	d< d S d S d S d S d S )Nr`   	ReduceSumr   r$   )
r   r   r   r   rN   r2   nodes_to_remover   r   r:   )r   rB   r*   rS   
mask_indexr   mask_input_namer   r   r    r     s    

z"FusionEmbedLayerNormalization.fuse)r   r   r   r   r   r   r   r   r   r   r    r     s    r   N)loggingr   typingr   r   r   r   fusion_baser   fusion_utilsr   onnxr	   r
   r   
onnx_modelr   r   r=   r   r   r   r   r   r    <module>   s         