o
    i e0[                     @   s   d Z ddlm  mZ ddlmZ ddlmZ ddlm	Z	 ddlm
Z
 ddlmZ edG d	d
 d
ejjjZdd ZG dd dejZdd Zdd Zdd ZdS )z9Library for exporting inference-only Keras models/layers.    N)keras_export)
base_layer)
functional)
sequential)io_utilszkeras.export.ExportArchivec                   @   s^   e Zd ZdZdd Zejjjdd Z	dddZ
d	d
 ZdddZdd Zdd Zdd ZdS )ExportArchivea	  ExportArchive is used to write SavedModel artifacts (e.g. for inference).

    If you have a Keras model or layer that you want to export as SavedModel for
    serving (e.g. via TensorFlow-Serving), you can use `ExportArchive`
    to configure the different serving endpoints you need to make available,
    as well as their signatures. Simply instantiate an `ExportArchive`,
    use `track()` to register the layer(s) or model(s) to be used,
    then use the `add_endpoint()` method to register a new serving endpoint.
    When done, use the `write_out()` method to save the artifact.

    The resulting artifact is a SavedModel and can be reloaded via
    `tf.saved_model.load`.

    Examples:

    Here's how to export a model for inference.

    ```python
    export_archive = ExportArchive()
    export_archive.track(model)
    export_archive.add_endpoint(
        name="serve",
        fn=model.call,
        input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],
    )
    export_archive.write_out("path/to/location")

    # Elsewhere, we can reload the artifact and serve it.
    # The endpoint we added is available as a method:
    serving_model = tf.saved_model.load("path/to/location")
    outputs = serving_model.serve(inputs)
    ```

    Here's how to export a model with one endpoint for inference and one
    endpoint for a training-mode forward pass (e.g. with dropout on).

    ```python
    export_archive = ExportArchive()
    export_archive.track(model)
    export_archive.add_endpoint(
        name="call_inference",
        fn=lambda x: model.call(x, training=False),
        input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],
    )
    export_archive.add_endpoint(
        name="call_training",
        fn=lambda x: model.call(x, training=True),
        input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],
    )
    export_archive.write_out("path/to/location")
    ```

    **Note on resource tracking:**

    `ExportArchive` is able to automatically track all `tf.Variables` used
    by its endpoints, so most of the time calling `.track(model)`
    is not strictly required. However, if your model uses lookup layers such
    as `IntegerLookup`, `StringLookup`, or `TextVectorization`,
    it will need to be tracked explicitly via `.track(model)`.

    Explicit tracking is also required if you need to be able to access
    the properties `variables`, `trainable_variables`, or
    `non_trainable_variables` on the revived archive.
    c                 C   s*   g | _ i | _tj| _g | _g | _g | _d S N)_endpoint_names_endpoint_signaturestf__version__tensorflow_version	variablestrainable_variablesnon_trainable_variablesself r   J/var/www/myenv/lib/python3.10/site-packages/keras/src/export/export_lib.py__init__]   s   
zExportArchive.__init__c                 C   s   t |tjjjstdt| d| t |tjr!|j	s!tdt
| ds)g | _| j| t |tjrO|  j|j7  _|  j|j7  _|  j|j7  _dS dS )z;Track the variables (and other assets) of a layer or model.zInvalid resource type. Expected an instance of a TensorFlow `Trackable` (such as a Keras `Layer` or `Model`). Received instead an object of type 'z'. Object received: zJThe layer provided has not yet been built. It must be built before export._trackedN)
isinstancer   __internal__tracking	Trackable
ValueErrortyper   Layerbuilthasattrr   appendr   r   r   )r   resourcer   r   r   tracke   s*   
zExportArchive.trackNc                 C   s   || j v rtd| d|rtj||d}|| j|< nt|tjjjr3|	 s0td| d|}ntdt
| || | j | dS )a  Register a new serving endpoint.

        Arguments:
            name: Str, name of the endpoint.
            fn: A function. It should only leverage resources
                (e.g. `tf.Variable` objects or `tf.lookup.StaticHashTable`
                objects) that are available on the models/layers
                tracked by the `ExportArchive` (you can call `.track(model)`
                to track a new model).
                The shape and dtype of the inputs to the function must be
                known. For that purpose, you can either 1) make sure that
                `fn` is a `tf.function` that has been called at least once, or
                2) provide an `input_signature` argument that specifies the
                shape and dtype of the inputs (see below).
            input_signature: Used to specify the shape and dtype of the
                inputs to `fn`. List of `tf.TensorSpec` objects (one
                per positional input argument of `fn`). Nested arguments are
                allowed (see below for an example showing a Functional model
                with 2 input arguments).

        Example:

        Adding an endpoint using the `input_signature` argument when the
        model has a single input argument:

        ```python
        export_archive = ExportArchive()
        export_archive.track(model)
        export_archive.add_endpoint(
            name="serve",
            fn=model.call,
            input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],
        )
        ```

        Adding an endpoint using the `input_signature` argument when the
        model has two positional input arguments:

        ```python
        export_archive = ExportArchive()
        export_archive.track(model)
        export_archive.add_endpoint(
            name="serve",
            fn=model.call,
            input_signature=[
                tf.TensorSpec(shape=(None, 3), dtype=tf.float32),
                tf.TensorSpec(shape=(None, 4), dtype=tf.float32),
            ],
        )
        ```

        Adding an endpoint using the `input_signature` argument when the
        model has one input argument that is a list of 2 tensors (e.g.
        a Functional model with 2 inputs):

        ```python
        model = keras.Model(inputs=[x1, x2], outputs=outputs)

        export_archive = ExportArchive()
        export_archive.track(model)
        export_archive.add_endpoint(
            name="serve",
            fn=model.call,
            input_signature=[
                [
                    tf.TensorSpec(shape=(None, 3), dtype=tf.float32),
                    tf.TensorSpec(shape=(None, 4), dtype=tf.float32),
                ],
            ],
        )
        ```

        This also works with dictionary inputs:

        ```python
        model = keras.Model(inputs={"x1": x1, "x2": x2}, outputs=outputs)

        export_archive = ExportArchive()
        export_archive.track(model)
        export_archive.add_endpoint(
            name="serve",
            fn=model.call,
            input_signature=[
                {
                    "x1": tf.TensorSpec(shape=(None, 3), dtype=tf.float32),
                    "x2": tf.TensorSpec(shape=(None, 4), dtype=tf.float32),
                },
            ],
        )
        ```

        Adding an endpoint that is a `tf.function`:

        ```python
        @tf.function()
        def serving_fn(x):
            return model(x)

        # The function must be traced, i.e. it must be called at least once.
        serving_fn(tf.random.normal(shape=(2, 3)))

        export_archive = ExportArchive()
        export_archive.track(model)
        export_archive.add_endpoint(name="serve", fn=serving_fn)
        ```
        zEndpoint name 'z' is already taken.)input_signaturezThe provided tf.function 'z' has never been called. To specify the expected shape and dtype of the function's arguments, you must either provide a function that has been called at least once, or alternatively pass an `input_signature` argument in `add_endpoint()`.an  If the `fn` argument provided is not a `tf.function`, you must provide an `input_signature` argument to specify the shape and dtype of the function arguments. Example:

export_archive.add_endpoint(
    name='call',
    fn=model.call,
    input_signature=[
        tf.TensorSpec(
            shape=(None, 224, 224, 3),
            dtype=tf.float32,
        )
    ],
)N)r	   r   r   functionr
   r   typesexperimentalGenericFunction_list_all_concrete_functionssetattrr    )r   namefnr#   decorated_fnr   r   r   add_endpoint   s    
k
	zExportArchive.add_endpointc                 C   sj   t |tttfstdt| dtdd |D s+tdttdd |D  t| |t| dS )a  Register a set of variables to be retrieved after reloading.

        Arguments:
            name: The string name for the collection.
            variables: A tuple/list/set of `tf.Variable` instances.

        Example:

        ```python
        export_archive = ExportArchive()
        export_archive.track(model)
        # Register an endpoint
        export_archive.add_endpoint(
            name="serve",
            fn=model.call,
            input_signature=[tf.TensorSpec(shape=(None, 3), dtype=tf.float32)],
        )
        # Save a variable collection
        export_archive.add_variable_collection(
            name="optimizer_variables", variables=model.optimizer.variables)
        export_archive.write_out("path/to/location")

        # Reload the object
        revived_object = tf.saved_model.load("path/to/location")
        # Retrieve the variables
        optimizer_variables = revived_object.optimizer_variables
        ```
        zNExpected `variables` to be a list/tuple/set. Received instead object of type 'z'.c                 s   s    | ]	}t |tjV  qd S r   )r   r   Variable.0vr   r   r   	<genexpr>8  s    z8ExportArchive.add_variable_collection.<locals>.<genexpr>zgExpected all elements in `variables` to be `tf.Variable` instances. Found instead the following types: c                 s   s    | ]}t |V  qd S r   )r   r/   r   r   r   r2   <  s    N)r   listtuplesetr   r   allr)   )r   r*   r   r   r   r   add_variable_collection  s   z%ExportArchive.add_variable_collectionc                    s    j std   i } j D ]	} |||< qd j vr)  j d |d< tjj |||d d fdd j D }t	d| d	|  d
S )a  Write the corresponding SavedModel to disk.

        Arguments:
            filepath: `str` or `pathlib.Path` object.
                Path where to save the artifact.
            options: `tf.saved_model.SaveOptions` object that specifies
                SavedModel saving options.

        **Note on TF-Serving**: all endpoints registered via `add_endpoint()`
        are made visible for TF-Serving in the SavedModel artifact. In addition,
        the first endpoint registered is made visible under the alias
        `"serving_default"` (unless an endpoint with the name
        `"serving_default"` was already registered manually),
        since TF-Serving requires this endpoint to be set.
        z4No endpoints have been set yet. Call add_endpoint().serving_defaultr   )options
signaturesz

c                 3   s     | ]}t t ||V  qd S r   )_print_signaturegetattrr0   r*   r   r   r   r2   b  s
    
z*ExportArchive.write_out.<locals>.<genexpr>zSaved artifact at 'z+'. The following endpoints are available:

N)
r	   r   _filter_and_track_resources_get_concrete_fnr   saved_modelsavejoinr   	print_msg)r   filepathr9   r:   r*   	endpointsr   r   r   	write_out@  s.   

zExportArchive.write_outc                 C   s4   || j v r
t| |S t| |d}t| d S )z&Workaround for some SavedModel quirks.r@   r   )r
   r<   _trackable_childrenr3   values)r   endpointtracesr   r   r   r?   l  s   

zExportArchive._get_concrete_fnc                    s    fdd j D }t|S )Nc                       g | ]}  |qS r   r?   r=   r   r   r   
<listcomp>u      zBExportArchive._get_variables_used_by_endpoints.<locals>.<listcomp>)r	   _list_variables_used_by_fns)r   fnsr   r   r    _get_variables_used_by_endpointst  s   z.ExportArchive._get_variables_used_by_endpointsc                    s    fdd j D }t|\}}t||  _g  _ddlm} t drC jD ]}t	j
| }|D ]}t||rA j| q4q(dS dS )zBTrack resources used by endpoints / referenced in `track()` calls.c                    rK   r   rL   r=   r   r   r   rM   {  rN   z=ExportArchive._filter_and_track_resources.<locals>.<listcomp>r   )IndexLookupr   N)r	   rO   r3   _all_variables_misc_assets+keras.src.layers.preprocessing.index_lookuprR   r   r   r   trainTrackableViewdescendantsr   r    )r   rP   tvsntvsrR   rootrX   	trackabler   r   r   r>   x  s   


z)ExportArchive._filter_and_track_resourcesr   )__name__
__module____qualname____doc__r   r   r   r    no_automatic_dependency_trackingr"   r-   r7   rF   r?   rQ   r>   r   r   r   r   r      s    A

 
*,r   c                 C   s   t  }||  t| tjtjfr0tj	t
| j}t|tr't|dkr'|g}|d| j| n|  }|s:td|g}|d| j| || d S )N   servezSThe model provided has never called. It must be called at least once before export.)r   r"   r   r   
Functionalr   
Sequentialr   nestmap_structure_make_tensor_specinputsr3   lenr-   __call___get_save_specr   rF   )modelrD   export_archiver#   	save_specr   r   r   export_model  s   
rp   c                       sJ   e Zd ZdZ					d fdd	Zdd Zdd
dZ fddZ  ZS )ReloadedLayeras  Reload a Keras model/layer that was saved via SavedModel / ExportArchive.

    Arguments:
        filepath: `str` or `pathlib.Path` object. The path to the SavedModel.
        call_endpoint: Name of the endpoint to use as the `call()` method
            of the reloaded layer. If the SavedModel was created
            via `model.export()`,
            then the default endpoint name is `'serve'`. In other cases
            it may be named `'serving_default'`.

    Example:

    ```python
    model.export("path/to/artifact")
    reloaded_layer = ReloadedLayer("path/to/artifact")
    outputs = reloaded_layer(inputs)
    ```

    The reloaded object can be used like a regular Keras layer, and supports
    training/fine-tuning of its trainable weights. Note that the reloaded
    object retains none of the internal structure or custom methods of the
    original object -- it's a brand new layer created around the saved
    function.

    **Limitations:**

    * Only call endpoints with a single `inputs` tensor argument
    (which may optionally be a dict/tuple/list of tensors) are supported.
    For endpoints with multiple separate input tensor arguments, consider
    subclassing `ReloadedLayer` and implementing a `call()` method with a
    custom signature.
    * If you need training-time behavior to differ from inference-time behavior
    (i.e. if you need the reloaded object to support a `training=True` argument
    in `__call__()`), make sure that the training-time call function is
    saved as a standalone endpoint in the artifact, and provide its name
    to the `ReloadedLayer` via the `call_training_endpoint` argument.
    rc   NTc                    s$  t  j|||d tj|| _|| _|| _|| _t	| j|r't
| j|| _n|| jjv r5| jj| | _ntd| d|rct	| j|rMt
| j|| _n|| jjv r[| jj| | _ntd| d| jg}|ro|| j t|\}}	|D ]	}
| j|
dd qw|	D ]	}
| j|
dd qd| _d S )N)	trainabler*   dtypezThe endpoint 'zy' is neither an attribute of the reloaded SavedModel, nor an entry in the `signatures` field of the reloaded SavedModel. T)rr   F)superr   r   r@   load_reloaded_objrD   call_endpointcall_training_endpointr   r<   call_endpoint_fnr:   r   call_training_endpoint_fnr    rO   _add_existing_weightr   )r   rD   rw   rx   rr   r*   rs   all_fnsrY   rZ   r1   	__class__r   r   r     sB   



zReloadedLayer.__init__c                    s&   | j  j j j| fddd dS )zACalls add_weight() to register but not create an existing weight.c                     s    S r   r   )___weightr   r   <lambda>  s    z4ReloadedLayer._add_existing_weight.<locals>.<lambda>)r*   shapers   rr   getterN)
add_weightr*   r   rs   )r   r   rr   r   r   r   r{     s   

z"ReloadedLayer._add_existing_weightFc                 K   s.   |r| j r| j|fi |S | j|fi |S r   )rx   rz   ry   )r   ri   trainingkwargsr   r   r   call  s   zReloadedLayer.callc                    s(   t   }| j| j| jd}i ||S )N)rD   rw   rx   )rt   
get_configrD   rw   rx   )r   base_configconfigr}   r   r   r     s   
zReloadedLayer.get_config)rc   NTNN)F)	r]   r^   r_   r`   r   r{   r   r   __classcell__r   r   r}   r   rq     s    )=

rq   c                 C   s   t j| j| j| jdS )N)rs   r*   )r   
TensorSpecr   rs   r*   )xr   r   r   rh     s   rh   c                 C   sJ   |   d }|jdd}|d}d| dg|dd   }d|}|S )Nr   T)verbose
z* Endpoint ''rb   )r(   pretty_printed_signaturesplitrB   )r+   r*   concrete_fnpprinted_signaturelinesrI   r   r   r   r;   #  s   

r;   c           	      C   s   g }g }t  }t  }| D ]T}t|dr|j}nt|dr"| g}n|g}|D ]8}|jD ]}t||vr@|| |t| q,|jD ]}t||vr^t||vr^|| |t| qDq'q||fS )Nconcrete_functionsget_concrete_function)	r5   r   r   r   r   idr    addr   )	rP   r   r   trainable_variables_idsnon_trainable_variables_idsr+   r   r   r1   r   r   r   rO   ,  s2   





rO   )r`   tensorflow.compat.v2compatv2r    tensorflow.python.util.tf_exportr   keras.src.enginer   r   r   keras.src.utilsr   r   r   AutoTrackabler   rp   r   rq   rh   r;   rO   r   r   r   r   <module>   s      s	