Download groot/vla/common/utils/misc/array_tensor_utils.py from zhicao/dreamzero: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/zhicao/dreamzero/resolve/main/groot/vla/common/utils/misc/array_tensor_utils.py
- Command line
-
hf download hf://zhicao/dreamzero/groot/vla/common/utils/misc/array_tensor_utils.py
-
curl -L -o array_tensor_utils.py https://huggingface.co/zhicao/dreamzero/resolve/main/groot/vla/common/utils/misc/array_tensor_utils.py
10.5 kB
| """ | |
| Functions that work on nested structures of torch.Tensor or numpy array | |
| """ | |
| from typing import Any, Dict, List, Optional, Union | |
| import numpy as np | |
| import torch | |
| import tree | |
| from ..data_structure.tree_utils import ( | |
| copy_non_leaf, | |
| is_sequence, | |
| tree_assign_at_path, | |
| tree_value_at_path, | |
| ) | |
| from .functional_utils import make_recursive_func | |
| def is_array_tensor(obj): | |
| return isinstance(obj, (np.ndarray, torch.Tensor)) | |
| def is_numpy(obj): | |
| return isinstance(obj, np.ndarray) | |
| def is_tensor(obj): | |
| return torch.is_tensor(obj) | |
| def any_stack(xs: List, *, dim: int = 0): | |
| """ | |
| Works for both torch Tensor and numpy array | |
| """ | |
| def _any_stack_helper(*xs): | |
| x = xs[0] | |
| if isinstance(x, np.ndarray): | |
| return np.stack(xs, axis=dim) | |
| elif torch.is_tensor(x): | |
| return torch.stack(xs, dim=dim) | |
| elif isinstance(x, float): | |
| # special treatment for float, defaults to float32 | |
| return np.array(xs, dtype=np.float32) | |
| else: | |
| return np.array(xs) | |
| return tree.map_structure(_any_stack_helper, *xs) | |
| def any_concat(xs: List, *, dim: int = 0): | |
| """ | |
| Works for both torch Tensor and numpy array | |
| """ | |
| def _any_concat_helper(*xs): | |
| x = xs[0] | |
| if isinstance(x, np.ndarray): | |
| return np.concatenate(xs, axis=dim) | |
| elif torch.is_tensor(x): | |
| return torch.cat(xs, dim=dim) | |
| elif isinstance(x, float): | |
| # special treatment for float, defaults to float32 | |
| return np.array(xs, dtype=np.float32) | |
| else: | |
| return np.array(xs) | |
| return tree.map_structure(_any_concat_helper, *xs) | |
| def any_chunk(x, chunks: int, *, dim: int = 0, strict: bool = True) -> List[Any]: | |
| """ | |
| Works for both torch Tensor and numpy array | |
| Returns: | |
| list of chunked nested structures | |
| """ | |
| assert chunks >= 1 | |
| x_copies = [copy_non_leaf(x) for _ in range(chunks)] | |
| def _any_chunk_helper(path, x): | |
| if is_array_tensor(x): | |
| if isinstance(x, np.ndarray): | |
| chunked_values = np.split(x, chunks, axis=dim) | |
| else: | |
| chunked_values = torch.chunk(x, chunks, dim=dim) | |
| if path: | |
| for xc, chunked in zip(x_copies, chunked_values): | |
| tree_assign_at_path(xc, path, chunked) | |
| else: # top-level, no nested path | |
| for i, chunked in enumerate(chunked_values): | |
| x_copies[i] = chunked | |
| else: | |
| if strict: | |
| raise NotImplementedError(f"Cannot chunk type {type(x)}") | |
| else: | |
| return | |
| tree.map_structure_with_path(_any_chunk_helper, x) | |
| return x_copies | |
| def chunk_seq(arr, chunks: int, check_divide=True): | |
| """ | |
| Args: | |
| check_divide: True to force arr must divide n | |
| """ | |
| k, m = divmod(len(arr), chunks) | |
| if check_divide and m != 0: | |
| raise ValueError(f"Array len {len(arr)} does not divide chunks {chunks}") | |
| return (arr[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(chunks)) | |
| def any_zeros_like(x: Union[Dict, np.ndarray, torch.Tensor, int, float, np.number]): | |
| """Returns a zero-filled object of the same (d)type and shape as the input. | |
| The difference between this and `np.zeros_like()` is that this works well | |
| with `np.number`, `int`, `float`, and `jax.numpy.DeviceArray` objects without | |
| converting them to `np.ndarray`s. | |
| Args: | |
| x: The object to replace with 0s. | |
| Returns: | |
| A zero-filed object of the same (d)type and shape as the input. | |
| """ | |
| if isinstance(x, (int, float, np.number)): | |
| return type(x)(0) | |
| elif is_tensor(x): | |
| return torch.zeros_like(x) | |
| elif is_numpy(x): | |
| return np.zeros_like(x) | |
| else: | |
| raise ValueError( | |
| f"Input ({type(x)}) must be either a numpy array, a tensor, an int, or a float." | |
| ) | |
| def any_ones_like(x: Union[Dict, np.ndarray, torch.Tensor, int, float, np.number]): | |
| """Returns a one-filled object of the same (d)type and shape as the input. | |
| The difference between this and `np.ones_like()` is that this works well | |
| with `np.number`, `int`, `float`, and `jax.numpy.DeviceArray` objects without | |
| converting them to `np.ndarray`s. | |
| Args: | |
| x: The object to replace with 1s. | |
| Returns: | |
| A one-filed object of the same (d)type and shape as the input. | |
| """ | |
| if isinstance(x, (int, float, np.number)): | |
| return type(x)(1) | |
| elif is_tensor(x): | |
| return torch.ones_like(x) | |
| elif is_numpy(x): | |
| return np.ones_like(x) | |
| else: | |
| raise ValueError( | |
| f"Input ({type(x)}) must be either a numpy array, a tensor, an int, or a float." | |
| ) | |
| def any_zero_(x: Union[Dict, np.ndarray, torch.Tensor]): | |
| """ | |
| Apply in-place zero-out to a tensor, i.e. x.zero_() | |
| """ | |
| if is_tensor(x): | |
| x.zero_() | |
| elif is_numpy(x): | |
| x.fill(0) | |
| else: | |
| raise ValueError(f"Input ({type(x)}) must be either a numpy array or a tensor") | |
| def any_fill_(x: Union[Dict, np.ndarray, torch.Tensor], value): | |
| """ | |
| Apply in-place zero-out to a tensor, i.e. x.zero_() | |
| """ | |
| if is_tensor(x): | |
| x.fill_(value) | |
| elif is_numpy(x): | |
| x.fill(value) | |
| else: | |
| raise ValueError(f"Input ({type(x)}) must be either a numpy array or a tensor") | |
| def get_batch_size(x, strict: bool = False) -> int: | |
| """ | |
| Args: | |
| x: can be any arbitrary nested structure of np array and torch tensor | |
| strict: True to check all batch sizes are the same | |
| """ | |
| def _get_batch_size(x): | |
| if isinstance(x, np.ndarray): | |
| return x.shape[0] | |
| elif torch.is_tensor(x): | |
| return x.size(0) | |
| else: | |
| return len(x) | |
| xs = tree.flatten(x) | |
| if strict: | |
| batch_sizes = [_get_batch_size(x) for x in xs] | |
| assert all( | |
| b == batch_sizes[0] for b in batch_sizes | |
| ), f"batch sizes must all be the same in nested structure: {batch_sizes}" | |
| return batch_sizes[0] | |
| else: | |
| return _get_batch_size(xs[0]) | |
| def add_batch_dim(x): | |
| if is_numpy(x): | |
| return np.expand_dims(x, axis=0) | |
| elif is_tensor(x): | |
| return x.unsqueeze(0) | |
| else: | |
| raise NotImplementedError(f"Unsupported data structure: {type(x)}") | |
| def remove_batch_dim(x): | |
| if is_numpy(x): | |
| return np.squeeze(x, axis=0) | |
| elif is_tensor(x): | |
| return x.squeeze(0) | |
| else: | |
| raise NotImplementedError(f"Unsupported data structure: {type(x)}") | |
| def any_to_primitive(x): | |
| if isinstance(x, (np.ndarray, np.number, torch.Tensor)): | |
| return x.tolist() | |
| else: | |
| return x | |
| def any_get_shape(x): | |
| if is_numpy(x): | |
| return tuple(x.shape) | |
| elif is_tensor(x): | |
| return tuple(x.size()) | |
| else: | |
| raise NotImplementedError(f"Unsupported data structure: {type(x)}") | |
| def any_mean(x, dim: Optional[int] = None, keepdim: bool = False): | |
| if is_numpy(x): | |
| return np.mean(x, axis=dim, keepdims=keepdim) | |
| elif is_tensor(x): | |
| return torch.mean(x, dim=dim, keepdim=keepdim) | |
| else: | |
| raise NotImplementedError(f"Unsupported data structure: {type(x)}") | |
| def any_variance(x, dim: Optional[int] = None, keepdim: bool = False, unbiased: bool = False): | |
| if is_numpy(x): | |
| return np.var(x, axis=dim, keepdims=keepdim, ddof=1 if unbiased else 0) | |
| elif is_tensor(x): | |
| return torch.var(x, dim=dim, keepdim=keepdim, unbiased=unbiased) | |
| else: | |
| raise NotImplementedError(f"Unsupported data structure: {type(x)}") | |
| def any_describe_str(x, shape_only=False): | |
| """ | |
| Describe type, shape, device, data type (of np array/tensor) | |
| Very useful for debugging | |
| """ | |
| t = type(x) | |
| tname = type(x).__name__ | |
| if is_numpy(x): | |
| shape = list(x.shape) | |
| if x.size == 1: | |
| if shape_only: | |
| return f"np scalar: {x.item()} {shape}" | |
| else: | |
| return f"np scalar: {x.item()} {shape} {x.dtype}" | |
| else: | |
| if shape_only: | |
| return f"np: {shape}" | |
| else: | |
| return f"np: {shape} {x.dtype}" | |
| elif is_tensor(x): | |
| shape = list(x.size()) | |
| if x.numel() == 1: | |
| if shape_only: | |
| return f"torch scalar: {x.item()} {shape}" | |
| else: | |
| return f"torch scalar: {x.item()} {shape} {x.dtype} {x.device}" | |
| else: | |
| if shape_only: | |
| return f"torch: {shape}" | |
| else: | |
| return f"torch: {shape} {x.dtype} {x.device}" | |
| elif is_sequence(x): | |
| return f"{tname}[{len(x)}]" | |
| elif isinstance(x, str): | |
| return x | |
| elif x is None: | |
| return "None" | |
| elif np.issubdtype(t, np.number) or np.issubdtype(t, np.bool_): | |
| return f"{tname}: {x}" | |
| else: | |
| return f"{tname}" | |
| def any_describe(x, msg="", *, shape_only=False): | |
| # from omlet.utils import yaml_dumps | |
| from pprint import pprint | |
| if isinstance(x, str) and msg != "": | |
| x, msg = msg, x | |
| if msg: | |
| msg += ": " | |
| print(msg, end="") | |
| pprint(any_describe_str(x, shape_only=shape_only)) | |
| def any_slice(x, slice): | |
| """ | |
| Args: | |
| slice: you can use np.s_[...] to return the slice object | |
| """ | |
| if is_array_tensor(x): | |
| return x[slice] | |
| else: | |
| return x | |
| def any_assign(x, assign_value, slice): | |
| """ | |
| Recursive version of x[slice] = assign_value | |
| If structures of x and assign_value do not match, we will respect `assign_value` | |
| E.g. x = {'a': ..., 'b': ...}, assign_value = {'a': ...}, then 'b' will not change | |
| Use np.s_[...] to get advanced slicing | |
| """ | |
| def _any_assign_helper(path, v): | |
| y = tree_value_at_path(x, path) | |
| y[slice] = v | |
| tree.map_structure_with_path(_any_assign_helper, assign_value) | |
| def any_transpose_first_two_axes(x): | |
| """ | |
| util to convert between (L, B, ...) and (B, L, ...) | |
| """ | |
| if is_numpy(x): | |
| return np.swapaxes(x, 0, 1) | |
| elif is_tensor(x): | |
| return torch.swapaxes(x, 0, 1) | |
| else: | |
| raise ValueError(f"Input ({type(x)}) must be either a numpy array or a tensor.") | |