
    ^jۇ                       U d Z ddlmZ ddlZ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mZ  ej"                  e      Z e       rddlZdd	lmZ dd
lmZmZmZmZmZmZ i Zded<   i Z ded<   i Z!ded<   ejD                  d:d       Z#ejD                  d;d       Z$ejD                  d<d       Z%d<dZ&d<dZ'd=dZ(d>dZ)d?dZ*d=dZ+e,fZ-ded<    e       r1e-ej\                  ej^                  ej`                  ejb                  fz  Z-d@dZ2dAdBdZ3dCdZ4dDdZ5dEdZ6dFd Z7dGd!Z8d"Z9	 	 	 	 	 	 dHd#Z:dId$Z;i Z<d%ed&<   dJd'Z= e=d(      dKd)       Z> e=d*      dKd+       Z? e=d,d-      dKd.       Z@ e=d,d/      dKd0       ZAdKd1ZBejD                  dLd2       ZC	 	 	 	 	 	 dMd3ZDd4ZEd5ZFdNd6ZGdOd7ZHdMd8ZI	 	 	 	 	 	 dMd9ZJy)Pu  Shared export utilities used by all exporter backends.

Organised into five sections (search for the `# ── Name ──` banners):

- **Patch and fix registries** — backend-keyed `_PATCHES` / `_FX_NODE_FIXES` /
  `_FX_PROGRAM_FIXES` populated via `@register_patch(backend, *paths)` /
  `@register_fx_node_fix` / `@register_fx_program_fix`, applied via
  `apply_patches` / `apply_fx_node_fixes` / `apply_fx_program_fixes`.
- **Recursive structure traversal** — internal helpers (`_map_leaf_tensors`,
  `_iter_leaf_tensors`) that drive every other tensor utility.
- **Public tensor utilities** — `get_leaf_tensors`, `duplicate_leaf_tensors`,
  `cast_leaf_tensors`, and `prepare_for_export` (sets attention/experts impl,
  patches non-exportable patterns, strips output flags).
- **Export input preparers** — `@register_export_input_preparer(marker)`
  registry that precomputes the per-encoder kwargs (`cu_seqlens`, `position_ids`,
  audio chunks, …) the model would otherwise need data-dependent ops for.
- **Decomposition** — `decompose_prefill_decode` (split a generative forward
  into prefill + decode) and `decompose_multimodal` + `is_multimodal` (split a
  multimodal forward into one entry per submodule), backed by `_capture_forward`.
    )annotationsN)MutableMapping)Any   )logging)is_torch_available)PreTrainedModel)'get_vision_bilinear_indices_and_weightsget_vision_cu_seqlensget_vision_merged_shapeget_vision_nearest_position_idsget_vision_position_idsget_vision_window_indexz*dict[str, list[tuple[Any, str, callable]]]_PATCHESzdict[str, list[callable]]_FX_NODE_FIXES_FX_PROGRAM_FIXESc              #     K   t        | |      }t        | | ||             	 d t        | ||       y# t        | ||       w xY ww)zNSwap `obj.<attribute>` with `factory(original)` for the duration of the block.N)getattrsetattr)obj	attributefactoryoriginals       g/var/www/ramen.bs-engineer-server.com/venv/lib/python3.12/site-packages/transformers/exporters/utils.pypatch_attributer   Q   sE      sI&HCGH-.*Y)Y)s    A5 AAAc           	   #     K   t        j                         5 }| D ]"  \  }}}|j                  t        |||             $ d ddd       y# 1 sw Y   yxY ww)u   Install `(obj, attribute, factory)` patches for the duration of the block.

    Plural form of `patch_attribute` — each `factory(original)` returns the replacement
    callable. Originals are restored on exit, even if the body raises.
    N)
contextlib	ExitStackenter_contextr   )patchesstackr   r   r   s        r   patch_attributesr"   \   s\      
			 5'. 	J#CGY HI	J  s   A,A	AAAc              #  |   K   t        t        j                  | g             5  d ddd       y# 1 sw Y   yxY ww)z:Install `_PATCHES[backend]` for the duration of the block.N)r"   r   get)backends    r   apply_patchesr&   i   s2      
(,,w3	4   s    <0	<9<c                      fd}|S )zKAppend the decorated `(gm, node) -> bool` fix to `_FX_NODE_FIXES[backend]`.c                R    t         j                  g       j                  |        | S N)r   
setdefaultappendfnr%   s    r   	decoratorz'register_fx_node_fix.<locals>.decorators   s#    !!'2.55b9	     r%   r.   s   ` r   register_fx_node_fixr2   p   s     r/   c                      fd}|S )u  Append the decorated `(exported_program) -> None` fix to `_FX_PROGRAM_FIXES[backend]`.

    Use this for fixes that need program-level context (range_constraints, graph_signature,
    state_dict) — the per-node `_FX_NODE_FIXES` shape only sees one node at a time.
    c                R    t         j                  g       j                  |        | S r)   )r   r*   r+   r,   s    r   r.   z*register_fx_program_fix.<locals>.decorator   s#    $$Wb188<	r/   r0   r1   s   ` r   register_fx_program_fixr5   z   s     r/   c                J    t         j                  | g       D ]
  } ||        y)zDApply `_FX_PROGRAM_FIXES[backend]` to `exported_program` (in place).N)r   r$   )r%   exported_programfixs      r   apply_fx_program_fixesr9      s'     $$Wb1 r/   c                      fd}|S )um  Append the decorated `factory(original)` to `_PATCHES[backend]`, once per `path`.

    Each `path` is a dotted Python path like `"torch.where"`, `"torch.Tensor.unsqueeze"`,
    or `"transformers.models.nllb_moe.modeling_nllb_moe.NllbMoeTop2Router._cast_classifier"`.
    The rightmost segment is the attribute to swap; the rest is the object that owns it.
    Paths are resolved at decoration time — submodules are imported as needed, falling
    back to `getattr` for class attributes. A path that fails to resolve (e.g. the backend
    isn't installed) is silently skipped so the module still imports.

    Passing multiple paths registers the SAME factory against each — useful for swapping
    the same method or torch op across several call sites (e.g. ``torch.unsqueeze`` +
    ``torch.Tensor.unsqueeze``, or one vision-attention forward across N model classes).
    c                    D ]M  }|j                  d      \  }}}t        |      }|&t        j                  g       j	                  ||| f       O | S )N.)
rpartition_resolve_dotted_pathr   r*   r+   )r-   pathobj_path_r   r   r%   pathss         r   r.   z!register_patch.<locals>.decorator   sd     	JD%)__S%9"Ha&x0C{,33S)R4HI	J 	r/   r0   )r%   rB   r.   s   `` r   register_patchrC      s     r/   c                   ddl }| j                  d      }	 |j                  |d         }|dd D ]#  }	 |j                  |j                   d|       }% |S # t        t
        f$ r t        ||      }Y Ew xY w# t        t
        f$ r Y yw xY w)u   Resolve a dotted Python path to the actual object — importing submodules where
    possible, falling back to `getattr` for class attributes (e.g. `torch.Tensor`).
    Returns `None` if the path can't be resolved (e.g. the backend isn't installed).r   Nr<      )	importlibsplitimport_module__name__ImportErrorAttributeErrorr   )r?   rF   partsr   parts        r   r>   r>      s     JJsOE	%%eAh/!"I 	)D)--av.FG	)
 
  0 )c4() ( s4   A9  AA9 A63A9 5A66A9 9B
Bc                   t         j                  | g       }|j                         D ]  }t        |t        j
                  j                        s(t        |j                  j                        D ]$  }|j                  dk7  r|D ]  } |||      s $ & 	 |j                  j                          |j                           y# t        t        f$ r Y w xY w)u  Walk every call_function node and apply the first matching `_FX_NODE_FIXES[backend]`
    fix, then DCE.

    Each fix has signature `(gm, node) -> bool`. Returning `True` means the fix consumed
    the node — no further fixes run against it. Fixes are expected to be disjoint by
    `node.target`; if multiple could apply, list order decides.

    After the walk, `Graph.eliminate_dead_code` runs on every sub-GraphModule and
    `gm.recompile()` is called once. PyTorch DCE occasionally raises `SystemError` /
    `KeyError` from `erase_node._update_args_kwargs` on orphaned symbolic-size nodes —
    we swallow both; any survivors are handled by the downstream backend optimizer.
    call_functionN)r   r$   modules
isinstancetorchfxGraphModulelistgraphnodesopeliminate_dead_code	recompileSystemErrorKeyError)r%   graph_modulefixesgmnoder8   s         r   apply_fx_node_fixesra      s     w+E""$ "ehh223( 	Dww/) r4=		HH((*LLN X& 		s   *CCCztuple[type, ...]_LEAF_SKIP_TYPESc           	        t        | t              r| S t        | t        j                        r |       S t        | t        t
        t        f      r t        |       fd| D              S t        | t              r$t	        |       D ]  }t        | |         | |<    | S t        | d      r8t        |       j                         D ]  \  }}t        | |t        |              | S )u  Apply `fn` to every tensor in a nested structure, preserving container types.

    Mutates dicts and `__dict__`-bearing objects in place (preserving identity — callers
    rely on this so downstream pops/mutations propagate back to the original mapping);
    rebuilds lists/tuples/sets/frozensets (immutable or order-sensitive containers).
    Skips non-traversable leaf types (enum, SymInt, etc.).
    c              3  6   K   | ]  }t        |        y wr)   _map_leaf_tensors).0itemr-   s     r   	<genexpr>z$_map_leaf_tensors.<locals>.<genexpr>   s     E*44Es   __dict__)rQ   rb   rR   TensorrU   tuplesettypedictrf   hasattrvarsitemsr   )r   r-   kattrattr_vals    `   r   rf   rf      s     #'(
#u||$#w#eS)*tCyEEEE#tc 	3A&s1vr2CF	3
sJ"3ioo/ 	@ND(C02>?	@Jr/   c              #    K   t        | t              ryt        | t        j                        r|xs d| f yt        | t        t
        t        f      r<t        |       D ]-  \  }}|r| d| n
t        |      }t        ||      E d{    / yt        | t              r8| j                         D ]$  \  }}|r| d| n|}t        ||      E d{    & yt        | d      rt        t        |       |      E d{    yy7 z7 47 
w)zEYield `(dotted_path, tensor)` for every tensor in a nested structure.Noutputr<   rj   )rQ   rb   rR   rk   rU   rl   rm   	enumeratestr_iter_leaf_tensorsro   rr   rp   rq   )r   prefixindexrh   r?   keyvalues          r   rz   rz      s     #'(#u||$ #%%	C$s+	,$S> 	6KE4*0fXQug&c%jD)$555	6 
C	))+ 	7JC(.fXQse$CD)%666	7 
j	!%d3i888 
" 6 78s7   BD	D	AD	D+D	<D=D	D	D	c                *    t        t        |             S )a  Recursively retrieve all leaf tensors from a potentially nested structure.

    Args:
        obj (`Any`):
            A tensor, dataclass, dict, list, tuple, or any nesting thereof.

    Returns:
        `dict[str, torch.Tensor]`: Flat mapping from dotted path strings to tensors.
    )ro   rz   )r   s    r   get_leaf_tensorsr     s     "3'((r/   c                <    t               dfd}t        | |      S )a  Clone tensors that appear more than once in an output structure.

    When a model returns the same tensor under two output names (e.g. `last_hidden_state`
    and `hidden_states[0]`), the ONNX optimizer deduplicates the two output nodes and
    renames one, breaking the expected name mapping. Cloning duplicates gives each output
    leaf a distinct identity so the optimizer has nothing to merge.
    c                v    t        |       v r| j                         S j                  t        |              | S r)   )idcloneadd)tensorseens    r   _dedupz&duplicate_leaf_tensors.<locals>._dedup+  s0    f:<<>!Fr/   r   torch.Tensorreturnr   )rm   rf   )r   r   r   s     @r   duplicate_leaf_tensorsr   !  s      5D S&))r/   c                ,    dfd}t        | |      S )zJRecursively cast all floating-point tensors to the given dtype and device.c                n    | j                         r| j                        S | j                        S )Ndtypedevice)r   )is_floating_pointto)r   r   r   s    r   _castz cast_leaf_tensors.<locals>._cast7  s6    8>8P8P8RvyyuVy4pX^XaXaioXaXppr/   r   re   )r   r   r   r   s    `` r   cast_leaf_tensorsr   4  s    q S%((r/   c                    t        | d      r| j                  S 	 t        | j                               j                  S # t        $ r Y yw xY w)a#  `.device` for any `nn.Module`. `PreTrainedModel` exposes it directly via `ModuleUtilsMixin`;
    for plain submodules (e.g. a `Linear` or `MultiModalProjector` from a decomposed multimodal model)
    we fall back to the first parameter. Returns `None` if the module has no parameters at all.r   N)rp   r   next
parametersStopIterationmodels    r   module_devicer   =  sI     uh||E$$&'...    "= 	A	A	c                    t        | d      r| j                  S 	 t        | j                               j                  S # t        $ r Y yw xY w)zE`.dtype` for any `nn.Module`. Same fallback story as `module_device`.r   N)rp   r   r   r   r   r   s    r   module_dtyper   I  sG    ug{{E$$&'--- r   )	use_cacheoutput_attentionsoutput_hidden_statesreturn_dictreturn_lossc                   dD ](  }|j                  |d      }|t        d| d| d       t        | d      r"t        | j                  dd      rt        d	      |j                  dd      rt        d
      t        D ci c]  }||v s||j                  |       }}t        j                         5  t        | |       ddd       t        |       }t        |       }||t        |||      }| ||fS c c}w # 1 sw Y   ;xY w)u6  Configure model and inputs for export. Mutates both `model` and `inputs` in place,
    returning `(model, inputs, output_flags)` where `output_flags` holds the values popped
    from `inputs` for `use_cache`, `return_dict`, etc. (to be applied reversibly onto
    `model.config` by `patch_model_config` during the trace).

    - Strips label inputs (`labels`, `future_values`) — loss computation is unsupported.
    - Pops output flags (`use_cache`, `return_dict`, …) from `inputs` so they don't appear
      as traced kwargs; the values are returned for the trace block to apply onto
      `model.config`.
    - Pre-computes data-dependent vision/audio kwargs registered via
      `@register_export_input_preparer` and writes them into `inputs`.
    - Casts input tensors to match the model's `dtype` / `device`.
    )labelsfuture_valuesNzFound 'zM' in inputs. Loss computation is not supported during export. Please remove 'z+' from your inputs before calling export().configr   FzFound 'model.config.return_loss=True'. Loss computation is not supported during export. Please set 'model.config.return_loss=False' before calling export().zFound 'return_loss=True' in inputs. Loss computation is not supported during export. Please remove 'return_loss' from your inputs or set it to False.r   )pop
ValueErrorrp   r   r   r$   _OUTPUT_FLAGSrR   no_gradprecompute_export_inputsr   r   r   )r   inputs	label_keyr~   flagoutput_flagsr   r   s           r   prepare_for_exportr   W  s8   " 1 	

9d+) %""+,WY  uhGELL-$OS
 	
 zz-'O
 	
 8EWtPVD&**T**WLW
 
 0 /0
 E5!FF."6vF&,&& X
0 0s   	C7C75C<<Dc                T    | j                         D ]  }t        ||d      x}|c S  y)zTReturn the first non-None value of `name` found on `model` or any of its submodules.N)rP   r   )r   namemoduler~   s       r   _find_submodule_attrr     s5    --/ VT400E=L r/   zdict[tuple[str, ...], callable]_EXPORT_INPUT_PREPARERSc                       fd}|S )u4  Register `fn(model, inputs) -> None`. Dispatched when every `marker` is a key in
    `inputs` with a non-`None` value — no model_type list to maintain. Use multiple
    markers to narrow the match when a single kwarg is too ambiguous (e.g.
    `("input_features", "feature_lens")` for omni audio encoders).c                    | t         <   | S r)   )r   )r-   markerss    r   r.   z1register_export_input_preparer.<locals>.decorator  s    +-(	r/   r0   )r   r.   s   ` r   register_export_input_preparerr     s     r/   grid_thwc                X   |d   }t        | d      }||j                  dd      }t        |      |d<   t        | d      du}t        |||      |d	<   t        | d
      }t        | d      }||t	        ||||      \  |d<   |d<   t        | d      }|t        |||      \  |d<   |d<   yy)a  Precompute helpers driven by `grid_thw`: `cu_seqlens`, `position_ids`, plus optional
    `window_index`/`cu_window_seqlens` (XNet-style window attn) and
    `bilinear_indices`/`bilinear_weights` (interpolation-based merging).

    Optional helpers are gated by the presence of their config attribute on the encoder
    (`window_size`+`patch_size` for window attention, `num_grid_per_side` for bilinear),
    so a model that doesn't use that feature won't get its kwarg injected.
    r   spatial_merge_sizeNmerge_sizesrE   
cu_seqlensaxis_dim)include_temporalposition_idswindow_size
patch_sizewindow_indexcu_window_seqlensnum_grid_per_sidebilinear_indicesbilinear_weights)r   r$   r   r   r   r
   )r   r   r   r   r   r   r   r   s           r   _prepare_grid_thw_vision_inputsr     s     j!H-e5IJ! $ZZq90:F< ,E:>dJ4X?QdtuF>&um<K%e\:J:#9>U(+z?
;~': ; -U4GH$Ah');B
>!"F+=$> %r/   target_sizesc                   |d   }t        | d      }|t        ||      |d<   t        | d      }|Wt        j                  j                  j                  |dd      }t        |d|d	   d
      \  |d<   |d<   t        ||      |d<   yy)a
  NaViT-style packed encoders carry per-image `(h, w)` as `target_sizes` instead of `grid_thw`.
    Synthesise `grid_thw = [1, h, w]` and run the nearest-position-id / window-index /
    merged-shape helpers so the per-image Python loops move outside the traced graph.r   num_patches_per_sideNr   window_kernel_size)rE   r   rE   )r~   r   )r   r   r   r   r   merged_shape)r   r   rR   nn
functionalpadr   r   )r   r   r   r   r   r   s         r   _prepare_navit_vision_inputsr     s    
 .)L/7MN'!@Oc!d~-e5IJ%88&&**<q*I>U8J18MZ[?
;~': ; "9GY!Z~ &r/   input_featuresfeature_lensc                   |d   }|d   }t         j                  t        |       j                     }t	        |d      }t	        |d      }t	        |d      } |||| j
                        \  }}	||d<   |	|d<   t        | d      r9 ||	|| j                  | j
                        |d	<    ||	| j
                        |d
<   y ||	      |d	<    ||	      |d
<    t	        |d      |      |d<   y)u  Replace `input_features`/`feature_lens` with precomputed `padded_feature`, `chunk_lengths`,
    `cu_seqlens`, `valid_indices` (+ `pool_indices` on Qwen2.5-Omni-style encoders) so the
    encoder's `.split(.tolist(), dim=0)` and related data-dependent ops happen outside the
    traced graph.

    The helpers (`chunk_and_pad_features`, `get_audio_cu_seqlens`, …) all live in the model's
    own ``modeling_*.py`` module, so we resolve them via ``type(model).__module__`` rather than
    hard-coding one Omni variant. ``n_window_infer`` selects the Qwen3-Omni-style four-arg
    ``get_audio_cu_seqlens`` over the Qwen2.5-Omni-style single-arg form.
    r   r   chunk_and_pad_featuresget_audio_cu_seqlensget_valid_indicespadded_featurechunk_lengthsn_window_inferr   valid_indicesget_pool_indicespool_indicesN)sysrP   rn   
__module__r   n_windowrp   r   )
r   r   r   r   r   r   r   r   r   r   s
             r   _prepare_omni_audio_inputsr     s    .)L,-N[[e//0F$V-EF"6+AB(;<$:><Y^YgYg$h!NM-F+F?u&'3M<QVQeQeglguguv|"3M5>>"R3MB|"3M"B!D1C!D\!R~r/   input_features_maskc                   ddl m} t        | d      }t        | d      }||y|d   }|j                  \  }}||dz  z  }|j	                  d      j                  t        j                        }	|j                  ||d      j	                  d      j                  d      j                  t        j                        }
 ||
|	||      |d	<   y)
u4  Precompute `cu_seqlens` for Qwen3-ASR — the encoder's call to ``get_audio_cu_seqlens``
    has a data-dependent Python loop that we evaluate here so the encoder pops the result
    from ``kwargs``. Mirrors the few lines that build ``feature_lens``/``chunk_lengths`` in
    ``Qwen3ASREncoder.forward``.
    r   )r   r   r   Nr   )dimr   )
#models.qwen3_asr.modeling_qwen3_asrr   r   shapesumr   rR   longviewreshape)r   r   r   r   r   r   
batch_sizepadded_feature_length
num_chunksr   r   s              r   _prepare_qwen3_asr_audio_inputsr     s     K#E:6H)%1ABN>1 !67(;(A(A%J%&8a<8J&**2.11%**=L',,ZRHLLQSLT\\]_`ccdidndnoM/|^]efF<r/   c                $   j                  d      t        | d      rj                  d      }j                  d      }|du xs% |du xs |j                  d   |j                  d   k(  }|rdt        t	        j
                  | j                        j                        }|D ci c]  }|v s||    }} | j                  di |\  }}	|d<   t        j                         D ]#  \  }
}t        fd|
D              s ||        % yc c}w )	u  Inject precomputed tensors for data-dependent ops the model would otherwise hit during tracing.

    Two layers:
    - Outer LLM rope index (`get_rope_index`) — generic `hasattr` probe; covers Qwen-VL / GLM-4V etc.
    - Per-encoder preparer dispatched by marker kwargs present in `inputs` (e.g. `grid_thw`,
      `target_sizes`, `(input_features, feature_lens)`) — see `register_export_input_preparer`.
      A preparer fires only when every one of its markers is present in `inputs`.
    r   Nget_rope_index	input_idsattention_maskrE   c              3  D   K   | ]  }j                  |      d u  y wr)   )r$   )rg   mr   s     r   ri   z+precompute_export_inputs.<locals>.<genexpr>3  s     :Qvzz!}D(:s    r0   )r$   rp   r   rm   inspect	signaturer   r   r   rr   all)r   r   r   	attn_mask
is_prefillrope_paramsrs   rope_inputsr   rA   r   preparers    `          r   r   r     s    zz.!)ge=M.NJJ{+	JJ/0	$&g)t*;gyq?QU^UdUdefUg?g
g//0D0DEPPQK1<LAV1fQi<LKL2e22A[AOL!%1F>" 5::< $:'::UF#$ Ms   $	D.Dc              #     K   g | j                   t        j                        t        j                        fd       }|| _         	  | _         y# | _         w xY ww)zCapture forward call kwargs into a list (one dict per call).

    Positional args are normalised to kwargs via `inspect.signature` so the
    captured dicts can be passed directly as `kwargs=inputs` to `torch.export`.
    c                    i } 	j                   | i |}|j                  j                         D ]  \  }}	j                  |   }|j                  t
        j                  j                  k(  r%|j                  t        j                  |             a|j                  t
        j                  j                  k7  st        j                  |      ||<    j                  |        | i |S r)   )bind	argumentsrr   r   kindr   	ParameterVAR_KEYWORDupdatecopydeepcopyVAR_POSITIONALr+   )
argskwargscapturedboundr   r~   paramcallsr   sigs
          r   wrapperz!_capture_forward.<locals>.wrapperK  s    $)&) ??002 	6KD%NN4(EzzW..:::e 45w00???!%u!5	6 	X(((r/   N)forwardr   r   	functoolswraps)r   r  r  r   r  s     @@@r   _capture_forwardr  ?  sc      E~~H


H
%C__X
) 
) FN"!s   A
A(A A(	A%%A(c           
        	 t        |       5 } | j                  di t        j                  |      ddd ddd       t              dk  r.t        dt        |       j                   dt        |       d	      t        j                  |       |d
   ft        j                  |       |d   fdS # 1 sw Y   zxY w# t        $ rB}t        dt        |       j                   dt        |j                                d      |d}~ww xY w)u  Run `model.generate()` for 2 tokens and capture prefill and decode inputs.

    Reuses the full generation machinery so every architecture (decoder-only, SSM,
    encoder-decoder, multi-modal, …) gets correct inputs without reimplementing the loop.

    Returns:
        `dict[str, tuple[torch.nn.Module, dict]]`:
        `{"prefill": (model, prefill_inputs), "decode": (model, decode_inputs)}`
    r   )max_new_tokensmin_new_tokensNz$decompose_prefill_decode failed for . Inputs passed: z<. Make sure the inputs are compatible with model.generate().z6decompose_prefill_decode expected at least 2 calls to z;.forward() during generate(max_new_tokens=2), but captured z. This likely means generate() bypasses the top-level forward() (e.g. delegates to an inner model), so prefill/decode decomposition is not supported for this architecture.r   rE   )prefilldecoder0   )r  generater	  r
  	ExceptionRuntimeErrorrn   rI   rU   keyslen)r   r   r  es       r   decompose_prefill_decoder$  _  s    e$ 	XENNWT]]62W1UVW	X 5zA~DT%[EYEYDZ [??B5zl KVV
 	
 IIe$eAh/99U#U1X. #	X 	X 24;3G3G2H I"6;;=12 3IJ
 		s-   B= +B1B= 1B:6B= =	D=DD)multi_modal_projector	connectorembed_visionembed_audio)lm_headc                8   i }d}dD ]&  }| j                  |      }||| us||| d<   d}( | j                         }|	|| ur||d<   | | j                  hD ]6  }t        t        z   D ]$  }||vst        ||d      t        ||      ||<   & 8 |rd|vri S |S )u  Return `{attr_name: module}` for multi-modal submodules found on `model`.

    Uses the canonical `PreTrainedModel.get_encoder("image"/"audio")` and `get_decoder()`
    accessors for encoders and the language model. Projectors and `lm_head` are looked
    up by name on `model` and its `base_model` (e.g. `LlavaModel` under `LlavaForConditionalGeneration`).

    Only returns results when at least one modal encoder AND a language model are found —
    otherwise the model is not multi-modal and should be exported as a single unit.
    F)imageaudio)modalityN_encoderTlanguage_model)get_encoderget_decoder
base_model_MULTIMODAL_PROJECTOR_NAMES_MULTIMODAL_LM_HEAD_NAMESr   )r   foundhas_encoderr-  encoderdecoderrootr   s           r   _find_multimodal_submodulesr:    s     )+EK& ##X#6 7%#7+2EXJh'(K !Gwe3")(() 2/2KK 	2D5 WT4%>%J%dD1d	22
 *%7	Lr/   c                *    t        t        |             S )zTReturns `True` if the model is multi-modal with modal encoders and a language model.)boolr:  r   s    r   is_multimodalr=    s    +E233r/   c                   t        |       }|s"t        dt        |       j                   d      	 t	        j
                         5 }t        j                         5  |j                         D ci c]   \  }}||j                  t        |            " }}} | di t        j                  |       ddd       ddd       |j                         D ci c]  \  }}|   r||||   d   f c}}S c c}}w # 1 sw Y   HxY w# 1 sw Y   LxY w# t        $ rB}t        dt        |       j                   dt        |j!                                d      |d}~ww xY wc c}}w )	u  Capture inputs to each multi-modal submodule via a single forward pass.

    Detects all known multi-modal submodules by attribute name (vision tower, projector,
    language model, lm_head, …) and captures their forward kwargs during one
    `model(**inputs)` call.

    Each submodule is returned as a separate `name: (module, inputs)` entry for
    independent export. The token-merge step (e.g. `masked_scatter` for multi-modal models)
    is intentionally left outside the exported graphs — it is the caller's responsibility
    to assemble `inputs_embeds` from the encoder outputs before running the decoder.

    Returns:
        `dict[str, tuple[torch.nn.Module, dict]]`: One `name: (module, inputs)`
        entry per detected submodule (image/audio encoder, projector, language model, lm_head).

    Raises:
        `ValueError`: if no known multi-modal submodules are found on the model.
    z8decompose_multimodal found no multi-modal submodules on zB. Expected an image/audio encoder + language model, found neither.Nz decompose_multimodal failed for r  r<   r   r0   )r:  r   rn   rI   r   r   rR   r   rr   r   r  r	  r
  r  r   rU   r!  )r   r   
submodulesr!   r   r   submodule_inputsr#  s           r   decompose_multimodalrA    sw   & -U3JFtE{G[G[F\ ]O P
 	

	!!# 	+uemmo 	+XbXhXhXj HTfe))*:6*BCC    *DMM&)*		+ 	+ ',,.D&D! 	v'-b122  	+ 	+ 	+ 	+
  .tE{/C/C.DDUVZ[a[f[f[hViUjjkl
	
s_   D DC8.%C2
C81D9D E2C88D	=DD	D 	E=EEc                p    t        | |      }|d   \  }}t        |      s|S t        ||      }|d   |d<   |S )u  Decompose a generative model into independently exportable `(model, forward_inputs)` pairs.

    Runs `decompose_prefill_decode` to capture prefill and decode forward kwargs from a real
    `model.generate(**inputs, max_new_tokens=2)`. If the prefill is multi-modal (per `is_multimodal`),
    further splits it into one entry per submodule (vision/audio encoder, projector, language model,
    `lm_head`) via `decompose_multimodal`.

    Args:
        model: Generative model. Must support `model.generate(**inputs)`.
        inputs: **Generate** kwargs — what you'd pass to `model.generate(**inputs)`.

    Returns:
        `{component_name: (submodel, forward_inputs)}`. Keys are `"prefill"` / `"decode"` for
        plain generative models and `"<modality>_encoder"` / `"multi_modal_projector"` /
        `"language_model"` / `"lm_head"` / `"decode"` for multi-modal generative models.
    r  r  )r$  r=  rA  )r   r   stagesprefill_modelprefill_inputs
componentss         r   decompose_for_generationrG    sM    & &eV4F$*9$5!M>'%m^DJ!(+Jxr/   )r   r   r   ry   r   r   )r    zlist[tuple[Any, str, callable]])r%   ry   )r%   ry   r   None)r%   ry   rB   ry   )r?   ry   )r   r   r-   callabler   r   ) )r   r   r{   ry   )r   r   r   zdict[str, torch.Tensor])r   r   r   r   )r   r   r   ztorch.dtyper   ztorch.devicer   r   )r   !PreTrainedModel | torch.nn.Moduler   ztorch.device | None)r   rK  r   ztorch.dtype | None)r   rK  r   zMutableMapping[str, Any]r   zRtuple[PreTrainedModel | torch.nn.Module, MutableMapping[str, Any], dict[str, Any]])r   torch.nn.Moduler   ry   r   z
Any | None)r   ry   )r   rL  r   dict[str, Any]r   rH  )r   rL  )r   r	   r   rM  r   z'dict[str, tuple[torch.nn.Module, dict]])r   r	   r   zdict[str, torch.nn.Module])r   r	   r   r<  )K__doc__
__future__r   r   r	  enumr  r   r   collections.abcr   typingr   utilsr   utils.import_utilsr   
get_loggerrI   loggerrR   modeling_utilsr	   vision_utilsr
   r   r   r   r   r   r   __annotations__r   r   contextmanagerr   r"   r&   r2   r5   r9   rC   r>   ra   rn   rb   EnumSymIntSymFloatSymBoolrf   rz   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r$  r3  r4  r:  r=  rA  rG  r0   r/   r   <module>r_     sv   * #      
 *   3 
		H	% 0 " 8:
4 9,.) ./1 , 1 * * 	 	  6&J '+W " ,ELL%..%--PP09,
)*&)	 i4',4'6N4'W4'| <> 8 =
  
+!
 ,!
H  /[ 0[$   0.AS BS>   02GHg Ig*$H " ">""" -"N d ( "J4
)X$2,r/   