U
    KcU                     @   s  d dl Z d dlZd dl mZ d dlmZmZmZmZmZ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mZ eeef Zeeeed	f f Zd
d Ze	ee  e	edddZeeeed	f f edddZeeeg ef edddZeeeeee	e e	e ef dddZe	e e	e eedddZ eeeed	f f eeeeedddZ!dd Z"eedd d!d"Z#ed#d$d%Z$d1eeeeed'd(d)Z%d2eeeeed'd+d,Z&d-d. Z'ed/d0 Z(dS )3    N)Tensor)AnyCallableOptionalTupleUnionList)tree_flattentree_unflatten_broadcast_to_and_flattenTreeSpec   )	tree_map_)partial)_add_batch_dim_remove_batch_dim_vmap_decrement_nesting_vmap_increment_nesting.c                    s    dt   fdd}|S )Nzcfunctorch transforms don't yet support saved tensor hooks. Please open an issue with your use case.c               
      s0   t jj  | |W  5 Q R  S Q R X d S N)torchZautogradgraphZdisable_saved_tensors_hooks)argskwargsfmessage 7/tmp/pip-unpacked-wheel-gikjz4vx/functorch/_src/vmap.pyfn    s    z.doesnt_support_saved_tensors_hooks.<locals>.fn)	functoolswraps)r   r   r   r   r   "doesnt_support_saved_tensors_hooks   s
    r!   )flat_in_dims	flat_argsreturnc                    sZ   dd t | |D  t dkr(td rRt fdd D rRtd  d d S )	Nc                 S   s"   g | ]\}}|d k	r| |qS r   )size.0in_dimargr   r   r   
<listcomp>+   s    z0_validate_and_get_batch_size.<locals>.<listcomp>r   z/vmap: Expected at least one Tensor to vmap overc                 3   s   | ]}| d  kV  qdS )r   Nr   )r'   r%   Zbatch_sizesr   r   	<genexpr>/   s     z/_validate_and_get_batch_size.<locals>.<genexpr>zTvmap: Expected all tensors to have the same size in the mapped dimension, got sizes z for the mapped dimension)ziplen
ValueErrorany)r"   r#   r   r+   r   _validate_and_get_batch_size(   s    
r1   )batched_outputsr$   c                 C   s   t | trt| S dS )Nr   )
isinstancetupler.   )r2   r   r   r   _num_outputs6   s    
r5   )valuenum_elementserror_message_lambdar$   c                 C   s.   t | ts| f| S t| |kr*t| | S r   )r3   r4   r.   r/   )r6   r7   r8   r   r   r   	_as_tuple?   s
    


r9   )in_dimsr   funcr$   c           	      C   s  t | ts8t | ts8tdt| d|  dt|  dt|dkrXtdt| dt|\}}t| |}|d krtdt| d|  dt| d  d	| d	t	t
||D ]\}\}}t |ts|d k	rtdt| d|  d
| dt |tr4t |ts4tdt| d|  d
| dt| d	|d k	r||  k s\|| krtdt| d|  d
| d|  d|  d|  d|d k	r|dk r||  ||< qt|||||fS )Nvmap(z
, in_dims=zv, ...)(<inputs>): expected `in_dims` to be int or a (potentially nested) tuple matching the structure of inputs, got: .r   z)(<inputs>): got no inputs. Maybe you forgot to add inputs, or you are trying to vmap over a function with no inputs. The latter is unsupported.zb, ...)(<inputs>): in_dims is not compatible with the structure of `inputs`. in_dims has structure r   z but inputs has structure z, ...)(<inputs>): Got in_dim=zE for an input but in_dim must be either an integer dimension or None.z' for an input but the input is of type zT. We cannot vmap over non-Tensor arguments, please use None as the respective in_dimz> for some input, but that input is a Tensor of dimensionality z  so expected in_dim to satisfy -z <= in_dim < )r3   intr4   r/   	_get_nametyper.   r	   r   	enumerater-   r   dimr1   )	r:   r   r;   r#   	args_specr"   ir)   r(   r   r   r   _process_batched_inputsG   s<    
($(8rE   )r"   r#   
vmap_levelr$   c                    s"    fddt | |D }t||S )Nc                    s(   g | ] \}}|d kr|n
t || qS r   )r   r&   rF   r   r   r*   |   s   z*_create_batched_inputs.<locals>.<listcomp>)r-   r
   )r"   r#   rF   rC   batched_inputsr   rG   r   _create_batched_inputsy   s    
rI   )r2   out_dimsrF   
batch_sizer;   r$   c           
   	      s   t | \}|D ]:}t|tjr"qtdt dt dt| dqfdd}t| tjrttrzg}qttrt	dkr}d q|  nt
}|d kr|   fd	d
t||D }	t|	S )Nr<   z	, ...): `z%` must only return Tensors, got type z as a return.c                
      s2   t dt  d dtd  d d	d S )Nr<   , ..., out_dims=z`)(<inputs>): out_dims is not compatible with the structure of `outputs`. out_dims has structure r   z but outputs has structure r=   )r/   r?   r	   r   )r;   rJ   output_specr   r   incompatible_error   s    (z+_unwrap_batched.<locals>.incompatible_errorr   r   c                    s   g | ]\}}t | |qS r   )r   )r'   Zbatched_outputout_dim)rK   rF   r   r   r*      s   z#_unwrap_batched.<locals>.<listcomp>)r	   r3   r   r   r/   r?   r@   r>   r4   r.   r   r-   r
   )
r2   rJ   rF   rK   r;   Zflat_batched_outputsoutrN   flat_out_dimsZflat_outputsr   )rK   r;   rJ   rM   rF   r   _unwrap_batched   s(    *


rR   c                 C   s,   t | trd S tdt| d| dd S )Nr<   rL   z): `out_dims` must be an int or a python collection of ints representing where in the outputs the vmapped dimension should appear.)r3   r>   r/   r?   )xr;   rJ   r   r   r   
_check_int   s
    
rT   )rJ   r;   r$   c                 C   s&   t | trd S ttt|| d|  d S )N)r;   rJ   )r3   r>   r   r   rT   )rJ   r;   r   r   r   $_check_out_dims_is_int_or_int_pytree   s    
rU   r;   c                 C   s   t | dr| jS t| S )N__name__)hasattrrW   reprrV   r   r   r   r?      s    
r?   error)r;   r:   rJ   
randomnessr$   c                    s(   t  t  fdd}|S )a  
    vmap is the vectorizing map; ``vmap(func)`` returns a new function that
    maps :attr:`func` over some dimension of the inputs. Semantically, vmap
    pushes the map into PyTorch operations called by :attr:`func`, effectively
    vectorizing those operations.

    vmap is useful for handling batch dimensions: one can write a function
    :attr:`func` that runs on examples and then lift it to a function that can
    take batches of examples with ``vmap(func)``. vmap can also be used to
    compute batched gradients when composed with autograd.

    Args:
        func (function): A Python function that takes one or more arguments.
            Must return one or more Tensors.
        in_dims (int or nested structure): Specifies which dimension of the
            inputs should be mapped over. :attr:`in_dims` should have a
            structure like the inputs. If the :attr:`in_dim` for a particular
            input is None, then that indicates there is no map dimension.
            Default: 0.
        out_dims (int or Tuple[int]): Specifies where the mapped dimension
            should appear in the outputs. If :attr:`out_dims` is a Tuple, then
            it should have one element per output. Default: 0.
        randomness (str): Specifies whether the randomness in this
            vmap should be the same or different across batches. If 'different',
            the randomness for each batch will be different. If 'same', the
            randomness will be the same across batches. If 'error', any calls to
            random functions will error. Default: 'error'. WARNING: this flag
            only applies to random PyTorch operations and does not apply to
            Python's random module or numpy randomness.

    Returns:
        Returns a new "batched" function. It takes the same inputs as
        :attr:`func`, except each input has an extra dimension at the index
        specified by :attr:`in_dims`. It takes returns the same outputs as
        :attr:`func`, except each output has an extra dimension at the index
        specified by :attr:`out_dims`.

    .. warning:
        :func:`vmap` works best with functional-style code. Please do not
        perform any side-effects in :attr:`func`, with the exception of
        in-place PyTorch operations. Examples of side-effects include mutating
        Python data structures and assigning values to variables not captured
        in :attr:`func`.

    One example of using :func:`vmap` is to compute batched dot products. PyTorch
    doesn't provide a batched ``torch.dot`` API; instead of unsuccessfully
    rummaging through docs, use :func:`vmap` to construct a new function.

        >>> torch.dot                            # [D], [D] -> []
        >>> batched_dot = functorch.vmap(torch.dot)  # [N, D], [N, D] -> [N]
        >>> x, y = torch.randn(2, 5), torch.randn(2, 5)
        >>> batched_dot(x, y)

    :func:`vmap` can be helpful in hiding batch dimensions, leading to a simpler
    model authoring experience.

        >>> batch_size, feature_size = 3, 5
        >>> weights = torch.randn(feature_size, requires_grad=True)
        >>>
        >>> def model(feature_vec):
        >>>     # Very simple linear model with activation
        >>>     return feature_vec.dot(weights).relu()
        >>>
        >>> examples = torch.randn(batch_size, feature_size)
        >>> result = functorch.vmap(model)(examples)

    :func:`vmap` can also help vectorize computations that were previously difficult
    or impossible to batch. One example is higher-order gradient computation.
    The PyTorch autograd engine computes vjps (vector-Jacobian products).
    Computing a full Jacobian matrix for some function f: R^N -> R^N usually
    requires N calls to ``autograd.grad``, one per Jacobian row. Using :func:`vmap`,
    we can vectorize the whole computation, computing the Jacobian in a single
    call to ``autograd.grad``.

        >>> # Setup
        >>> N = 5
        >>> f = lambda x: x ** 2
        >>> x = torch.randn(N, requires_grad=True)
        >>> y = f(x)
        >>> I_N = torch.eye(N)
        >>>
        >>> # Sequential approach
        >>> jacobian_rows = [torch.autograd.grad(y, x, v, retain_graph=True)[0]
        >>>                  for v in I_N.unbind()]
        >>> jacobian = torch.stack(jacobian_rows)
        >>>
        >>> # vectorized gradient computation
        >>> def get_vjp(v):
        >>>     return torch.autograd.grad(y, x, v)
        >>> jacobian = functorch.vmap(get_vjp)(I_N)

    :func:`vmap` can also be nested, producing an output with multiple batched dimensions

        >>> torch.dot                            # [D], [D] -> []
        >>> batched_dot = functorch.vmap(functorch.vmap(torch.dot))  # [N1, N0, D], [N1, N0, D] -> [N1, N0]
        >>> x, y = torch.randn(2, 3, 5), torch.randn(2, 3, 5)
        >>> batched_dot(x, y) # tensor of size [2, 3]

    If the inputs are not batched along the first dimension, :attr:`in_dims` specifies
    the dimension that each inputs are batched along as

        >>> torch.dot                            # [N], [N] -> []
        >>> batched_dot = functorch.vmap(torch.dot, in_dims=1)  # [N, D], [N, D] -> [D]
        >>> x, y = torch.randn(2, 5), torch.randn(2, 5)
        >>> batched_dot(x, y)   # output is [5] instead of [2] if batched along the 0th dimension

    If there are multiple inputs each of which is batched along different dimensions,
    :attr:`in_dims` must be a tuple with the batch dimension for each input as

        >>> torch.dot                            # [D], [D] -> []
        >>> batched_dot = functorch.vmap(torch.dot, in_dims=(0, None))  # [N, D], [D] -> [N]
        >>> x, y = torch.randn(2, 5), torch.randn(5)
        >>> batched_dot(x, y) # second arg doesn't have a batch dim because in_dim[1] was None

    If the input is a Python struct, :attr:`in_dims` must be a tuple containing a struct
    matching the shape of the input:

        >>> f = lambda dict: torch.dot(dict['x'], dict['y'])
        >>> x, y = torch.randn(2, 5), torch.randn(5)
        >>> input = {'x': x, 'y': y}
        >>> batched_dot = functorch.vmap(f, in_dims=({'x': 0, 'y': None},))
        >>> batched_dot(input)

    By default, the output is batched along the first dimension. However, it can be batched
    along any dimension by using :attr:`out_dims`

        >>> f = lambda x: x ** 2
        >>> x = torch.randn(2, 5)
        >>> batched_pow = functorch.vmap(f, out_dims=1)
        >>> batched_pow(x) # [5, 2]

    For any function that uses kwargs, the returned function will not batch the kwargs but will
    accept kwargs

        >>> x = torch.randn([2, 5])
        >>> def f(x, scale=4.):
        >>>   return x * scale
        >>>
        >>> batched_pow = functorch.vmap(f)
        >>> assert torch.allclose(batched_pow(x), x * 4)
        >>> batched_pow(x, scale=x) # scale is not batched, output has shape [2, 2, 5]

    .. note::
        vmap does not provide general autobatching or handle variable-length
        sequences out of the box.
    c                     s6   t   t|  \}}}}t ||||f|S r   )rU   rE   
_flat_vmap)r   r   rK   r"   r#   rC   r;   r:   rJ   r[   r   r   wrappedf  s    
      zvmap.<locals>.wrapped)_check_randomness_argr   r    )r;   r:   rJ   r[   r^   r   r]   r   vmap   s
     r`      c                    sV   t  dkr tdS dd dd  t fdd}|S )	a  
    chunk_vmap is the vectorizing map (vmap) using chunks of input data. It is a mix of vmap (which vectorizes
    everything) and map (which executes things sequentially). ``chunk_vmap`` vectorizes the input with number of
    chunks at a time. For more details about vectorizing map, see :func:`vmap`.

    Args:
        func (function): A Python function that takes one or more arguments.
            Must return one or more Tensors.
        in_dims (int or nested structure): Specifies which dimension of the
            inputs should be mapped over. :attr:`in_dims` should have a
            structure like the inputs. If the :attr:`in_dim` for a particular
            input is None, then that indicates there is no map dimension.
            Default: 0.
        out_dims (int or Tuple[int]): Specifies where the mapped dimension
            should appear in the outputs. If :attr:`out_dims` is a Tuple, then
            it should have one element per output. Default: 0.
        randomness (str): Specifies whether the randomness in this
            vmap should be the same or different across batches. If 'different',
            the randomness for each batch will be different. If 'same', the
            randomness will be the same across batches. If 'error', any calls to
            random functions will error. Default: 'error'. WARNING: this flag
            only applies to random PyTorch operations and does not apply to
            Python's random module or numpy randomness.
        chunks (int): Number of chunks to use to split the input data. Default is 2.
            If equals to 1 then :func:`vmap` is called.

    Returns:
        Returns a new "batched" function. It takes the same inputs as
        :attr:`func`, except each input has an extra dimension at the index
        specified by :attr:`in_dims`. It takes returns the same outputs as
        :attr:`func`, except each output has an extra dimension at the index
        specified by :attr:`out_dims`.
    r   )r:   rJ   r[   c                    s(   t  fddt| |D }t| }|S )Nc                 3   s2   | ]*\}}|d k	r |j  |dn|g  V  qd S )NrB   )chunk)r'   tr(   chunks_r   r   r,     s   z;chunk_vmap.<locals>._get_chunk_flat_args.<locals>.<genexpr>)r4   r-   )Z
flat_args_Zflat_in_dims_rf   Zflat_args_chunkschunks_flat_argsr   re   r   _get_chunk_flat_args  s
    z(chunk_vmap.<locals>._get_chunk_flat_argsc                 S   sN   g }g }| D ]$}t |\}}|| || q|d }tt| }||fS )Nr   )r	   appendlistr-   )Zchunks_output_Zflat_chunks_outputZarg_spec_listoutputflat_outputZ	arg_specsarg_specflat_output_chunksr   r   r   _flatten_chunks_output  s    
z*chunk_vmap.<locals>._flatten_chunks_outputc                     s   t  t| \}}}}||}g }dkr>t nd }|D ]>}t||}	|d k	rft| |t|	|||f| qF |\}
}~t|}t	|t	|
kst
g }|D ]"}|tj|
d |d |
d= q~
t||S )Nsamer   rb   )rU   rE   r   Zget_rng_stater1   Zset_rng_stateri   r\   r   r.   AssertionErrorcatr
   )r   r   _r"   r#   rC   rg   Zchunks_outputrsrK   rn   rm   rQ   rl   rO   ro   rh   chunksr;   r:   rJ   r[   r   r   wrapped_with_chunks  s@    


      
z'chunk_vmap.<locals>.wrapped_with_chunks)r_   r`   r   r    )r;   r:   rJ   r[   rv   rw   r   ru   r   
chunk_vmapq  s    '
#rx   c                 C   s   | dkrt d|  d S )N)rZ   Z	differentrp   zLOnly allowed values for randomness are 'error', 'different', or 'same'. Got )RuntimeError)r[   r   r   r   r_     s    r_   c                 K   sD   t ||}z,t||||}	| |	|}
t|
|||| W S t  X d S r   )r   r   rI   rR   )r;   rK   r"   r#   rC   rJ   r[   r   rF   rH   r2   r   r   r   r\     s    

r\   )r   r   rZ   )r   r   rZ   ra   ))r   r   r   typingr   r   r   r   r   r   Ztorch.utils._pytreer	   r
   r   r   Zpytree_hacksr   r   Ztorch._C._functorchr   r   r   r   r>   Z	in_dims_tZ
out_dims_tr!   r1   r5   strr9   rE   rI   rR   rT   rU   r?   r`   rx   r_   r\   r   r   r   r   <module>   sz    
  		  3      )	     '    n