o
    ,wi+                     @   s&  d Z ddlZddlZddlZddlmZmZmZmZm	Z	m
Z
mZmZmZ ddlmZ ddlZedZedZeee edef f ZG dd	 d	ee ejZG d
d dee ejZdedefddZG dd deZejG dd dee ZejG dd dZe ZejZej Z ej!Z!ej"Z"dS )a  A registry-based system for constructing models.

## Skeletons and fixtures

Skeletons generally express a tree of modules; for example in an EncoderLayer,
one might construct Flax modules like so:

EncoderLayer
  attention: normalized_block()
    layer_norm: layer_norm()
    body: self_attention()
      wrapped: MultiHeadDotProductAttention()
    dropout: dropout()
  mlp: normalized_block()
    layer_norm: layer_norm()
    body: mlp()
      wrapped: MlpBlock()
    dropout: dropout()

and this becomes even more detailed when libraries are composed. In order to
mitigate some verbosity, skeletons allow these tree-like templates to be
indexed by the type of the module.

However, details of configuration, typically specific to a project like T5,
should be filled in later. We call this configuration a "fixture". Generally
"skeletons" should only make choices that are quite natural to the types
themselves; for example, if an EncoderLayer has an "attention" and "mlp" fields,
then those are almost always filled with normalized blocks containing attention
and MLP modules.

## Validators

Validators are another utility to separate concerns between module authors and
projects which may use those modules. A module author might know that their
MlpBlock() module must have a parameter `foo` specified whenever another
parameter `bar` is True, and it is convenient to express those invariants at
the level of the type (MlpBlock, in this case) rather than counting on each
project / experimenter to reproduce those checks.

## Global vs. local registries

In general, we are going to have a global type-based registry for constructing
"skeletons" of models, and then use functions that set configuration in a nested
manner. However, the underlying implementation here is rather unopinionated in
terms of how "global" its state is, so if specific projects find non-global
registries more useful, they can easily work with them.

TBD:

 * Examples with placeholders.
 * Add a function for validating configs by traversing the config tree.
    N)	AnyCallableDictGenericListOptionalTypeTypeVarUnion)configST.c                   @   $   e Zd Zdeje ddfddZdS )
SkeletonFncfgreturnNc                 C      d S N selfr   r   r   o/home/ubuntu/sommelier/.venv/lib/python3.10/site-packages/fiddle/_src/experimental/autobuilders/autobuilders.py__call__U      zSkeletonFn.__call____name__
__module____qualname__
config_libConfigr   r   r   r   r   r   r   S       r   c                   @   r   )ValidatorFnr   r   Nc                 C   r   r   r   r   r   r   r   r   [   r   zValidatorFn.__call__r   r   r   r   r   r!   Y   r    r!   	fn_or_clsr   c                 C   sF   z	t | du}W n ty   d}Y nw |o"t |  o"t |  S )av  Helper method for determining wheher a function or class can be configured.

  This will return False for builtin stuff like int/tuple/etc. We generally
  don't want to configure these classes, partly because they work without
  requisite arguments, for example int() is 0.

  Args:
    fn_or_cls: Function or class.

  Returns:
    Whether `fn_or_cls` can be configured.
  NF)inspect	signature
ValueError	isbuiltinismethod)r"   has_signaturer   r   r   _is_configurable_   s   

r)   c                       s2   e Zd ZdZdee f fddZdd Z  ZS )DuplicateSkeletonErrorzHIndicates that two skeletons were attempted to be registered for a type.r"   c                    s   t  d| d || _d S )NSkeletonFn for z already exists!)super__init__r"   )r   r"   	__class__r   r   r-   z   s   
zDuplicateSkeletonError.__init__c                 C   s6   t | j}t | j\}}d| j d| d| dS )Nr+   z (defined at :z) already exists!)r#   getsourcefiler"   getsourcelines)r   filename_line_numberr   r   r   __str__~   s   
zDuplicateSkeletonError.__str__)	r   r   r   __doc__FnOrClsr   r-   r6   __classcell__r   r   r.   r   r*   w   s    r*   c                   @   sB   e Zd ZU dZdZeee  ed< e	j
edZeee  ed< dS )
TableEntrya  An entry in the registry for a specific type or function.

  Attributes:
    skeleton_fn: A function which can create an initial configuration for this
      type or function.
    validators: A list of functions which can validate configuration objects of
      this type or function.
  Nskeleton_fndefault_factory
validators)r   r   r   r7   r;   r   r   r   __annotations__dataclassesfieldlistr>   r   r!   r   r   r   r   r:      s   
  r:   c                   @   s   e Zd ZU dZejdd dZeee	 e
e	 f ed< 	ddee ded	eje fd
dZdee d	eee gee f fddZ	ddee	 ded	dfddZdee d	eee gee f fddZdS )RegistryzAn AutoBuilder registry, which knows how to create a model skeleton.

  Attributes:
    table: A mapping from a type or function to configure, to a TableEntry.
  c                   C   s
   t tS r   )collectionsdefaultdictr:   r   r   r   r   <lambda>   s   
 zRegistry.<lambda>r<   tableTr"   require_skeletonr   c                 C   sD   t |}| j| }|jdu r|rtd| d|S || |S )a   Creates a configuration instance of a given function or class.

    Args:
      fn_or_cls: Function or class to get a skeleton fdl.Config instance for.
      require_skeleton: If True, a KeyError will be raised if a skeleton of the
        desired function/type is not available. If False, an empty fdl.Config of
        that function/class will be returned.

    Returns:
      Configuration instance.

    Raises:
      KeyError: If there is no skeleton registered for the given class, and
        require_skeleton is True (the default).
    NzNo skeleton registered for .)r   r   rG   r;   KeyError)r   r"   rH   base_configentryr   r   r   r      s   



zRegistry.configc                    s    fdd}|S )a  Registers a function as a skeleton for a given type.

    Example:

    @dataclasses.dataclass
    class MyDense:
      activation: Callable[..., Any]

    @ab.skeleton(MyDense):
    def my_dense_skeleton(config: fdl.Config):
      config.activation = fdl.Config(nn.gelu, approximate=True)

    Often the skeleton definition (`my_dense_skeleton`) can be kept separately
    from the modeling library code, to make the modeling library code agnostic
    to the choice of configuration library.

    Args:
      fn_or_cls: Function that the skeleton is configuring.

    Returns:
      Decorator that, when applied to a function definition, will register that
      function as a skeleton.
    c                    s&   j   }|jd urt | |_| S r   )rG   r;   r*   )r;   rL   r"   r   r   r   inner   s
   

z Registry.skeleton.<locals>.innerr   r   r"   rN   r   rM   r   skeleton   s   zRegistry.skeletonFattributes_require_skeletonsNc                    s   i  t |j D ]C\}}||v r|| dur||  |< q
|jt jjkr1td|d| dt|jsHt	d|d| d|j d| d	|j |< q
dt
jf fd	d
}|| dS )a  Registers a skeleton based on arg types.

    Sometimes functions are written to take a more generic class, for example
    one might have an encoder layer in Flax expressed like,

    class EncoderLayer(nn.Module):
      dropout: nn.Module

    but in practice, `dropout` is almost always `nn.Dropout`, the type signature
    is just left to be the most general thing in case of experimental overrides.

    In this case, we can specify that the dropout is `nn.Dropout` as follows:

    ab.auto_skeleton(EncoderLayer, dropout=nn.Dropout)

    This is equivalent to writing:

    @ab.skeleton(EncoderLayer)
    def encoder_layer_skeleton(config: fdl.Config[EncoderLayer]):
      config.dropout = ab.config(nn.Dropout, require_skeleton=False)

    By default, `auto_skeleton` doesn't require the attributes to have defined
    skeletons, but would use them if available. In this case, the `dropout`
    class attribute can be filled with a blank `fdl.Config(nn.Dropout)`, if
    there is not a skeleton for `nn.Dropout`.

    If you require more advanced skeleton setups, then please use the `skeleton`
    method.

    Args:
      fn_or_cls: Function or class to generate the skeleton for.
      attributes_require_skeletons: Whether skeletons are required for any class
        attributes or function arguments to `fn_or_cls`. By default, skeletons
        are not required, whereupon empty fdl.Config instances are created.
      **kwargs: Any override types to use for configuration.

    Raises:
      ValueError: If there is no annotation for a parameter not mentioned in
        **kwargs.
      TypeError: If one of the parameters not mentioned in **kwargs is not a
        configurable type.
    Nz
Parameter z of zY doesn't have an annotation, and no override type was set in the auto_skeleton decorator.z is of type z*, which cannot be configured. Please pass za=None to your auto-skeleton invocation to avoid automatically adding a Config for this parameter.r   c                    s,      D ]\}}t| |j|d qd S )N)rH   )itemssetattrr   )r   nametype_to_configurearg_to_typerQ   r   r   r   arg_type_skeleton  s   z1Registry.auto_skeleton.<locals>.arg_type_skeleton)r#   r$   
parametersrR   
annotation	Signatureemptyr%   r)   	TypeErrorr   r   rP   )r   r"   rQ   kwargsrT   	parameterrX   r   rV   r   auto_skeleton   s*   .
zRegistry.auto_skeletonc                    s$   dt t dt t f fdd}|S )a  Decorator to register a function as a validator for a given type.

    Args:
      fn_or_cls: Function that the validator is checking.

    Returns:
      Decorator that, when applied to a function definition, will register that
      function as a validator.
    validator_fnr   c                    s   j   j|  | S r   )rG   r>   append)ra   rM   r   r   rN   5  s   z!Registry.validator.<locals>.inner)r!   r   rO   r   rM   r   	validator(  s    zRegistry.validator)T)F)r   r   r   r7   r@   rA   rG   r   r8   r   r:   r?   r   boolr   r   r   r   r   rP   r`   r!   rc   r   r   r   r   rC      s>   
 

%
LrC   )#r7   rD   r@   r#   typingr   r   r   r   r   r   r   r	   r
   fiddle._srcr   r   typing_extensionsr   r   r8   Protocolr   r!   rd   r)   r%   r*   	dataclassr:   rC   _default_registryrP   r`   rc   r   r   r   r   <module>   s0   5, +
