Files
nvidia--tensorrt/tools/Polygraphy/polygraphy/util/array.py
T
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

1420 lines
37 KiB
Python

#
# SPDX-FileCopyrightText: Copyright (c) 1993-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
This file includes utility functions for arrays/tensors that work for multiple
libraries like NumPy and PyTorch.
"""
import builtins
import functools
import math
import numbers
from polygraphy import mod
from polygraphy.datatype import DataType
from polygraphy.logger import G_LOGGER
np = mod.lazy_import("numpy")
torch = mod.lazy_import("torch>=1.13.0")
@mod.export()
def is_torch(obj):
"""
Whether the provided object is a PyTorch tensor.
This function does *not* introduce a dependency on the PyTorch module.
Args:
obj (Any): The object to check.
Returns:
bool: Whether the object is a PyTorch tensor.
"""
return (
torch.is_installed() and torch.is_importable() and isinstance(obj, torch.Tensor)
)
@mod.export()
def is_numpy(obj):
"""
Whether the provided object is a NumPy array or scalar.
This function does *not* introduce a dependency on the NumPy module.
Args:
obj (Any): The object to check.
Returns:
bool: Whether the object is a NumPy array.
"""
return (
np.is_installed()
and np.is_importable()
and (isinstance(obj, np.ndarray) or isinstance(obj, np.generic))
)
@mod.export()
def is_device_view(obj):
"""
Whether the provided object is a DeviceView array.
Args:
obj (Any): The object to check.
Returns:
bool: Whether the object is a DeviceView.
"""
from polygraphy.cuda import DeviceView
return isinstance(obj, DeviceView)
# The current design dispatches to the correct function implementation separately for each function call.
# Obviously, this has some performance cost and an alternative approach would be a more familiar inheritance
# pattern wherein we would have a BaseArray class and then child classes like NumpyArray, TorchArray, PolygraphyDeviceArray etc.
# That way, the dispatching logic would only have to run once when we construct an instance of one of these
# classes.
#
# The tradeoff is that the caller would then have to be careful that they are *not* passing in NumPy arrays,
# Torch tensors etc. directly, but have first wrapped them appropriately. Plus, at the interface boundaries,
# we would have to unwrap them once again since we don't want to expose the wrappers at the API level (the user
# should be able to work directly with NumPy arrays, PyTorch tensors etc.).
#
# To illustrate this a bit better, consider the two possible workflows:
#
# Option 1 (dispatch logic in each function, current design):
#
# def my_api_func(obj)
# nbytes = util.array.nbytes(obj) # Dispatching logic needs to run on each function call
# dtype = util.array.dtype(obj)
# # Do something interesting, then...
# return obj
#
# Option 2 (class hierarchy, possible alternative design):
#
# # Assume we have:
#
# class BaseArray:
# ...
#
# class TorchArray:
# ...
#
# # etc.
#
# def my_api_func()
# obj = wrap_array(obj) # Dispatch logic only runs once
# nbytes = obj.nbytes
# dtype = obj.dtype
# # Do something interesting, then...
# return unwrap_array(obj) # Need to return the np.ndarray/torch.Tensor/DeviceView, *not* the wrapper
#
# In Polygraphy, the number of calls to `wrap_array`/`unwrap_array` would most likely be quite high
# relative to the number of calls to the actual methods, so the perfomance hit of the current implementation
# may not be that significant. If it is, then it should be straightforward, though time-consuming, to switch to Option 2.
#
def dispatch(num_arrays=1):
"""
Decorator that will dispatch to functions specific to a framework type, like NumPy or PyTorch,
based on the type of the input.
The decorated function should return a dictionary with implementations for all supported types.
The following keys may be specified: ["torch", "numpy", "device_view", "number"].
Args:
num_arrays (int):
The number of arrays expected.
The naming convention for the array arguments is as follows:
- For a single array, the argument is called "obj".
- For two arrays, the arguments are called "lhs" and "rhs".
- For N>2 arrays, the arguments are called "obj0", "obj1", ... "obj<N-1>"
In the case of more than one array, this function will automatically convert the rest to be of the
same kind as the first.
"""
def dispatch_impl(func):
def _get_key(obj):
key = None
if is_device_view(obj):
key = "device_view"
elif is_numpy(obj):
key = "numpy"
elif is_torch(obj):
key = "torch"
elif isinstance(obj, numbers.Number):
key = "number"
if not key:
G_LOGGER.critical(
f"Function: {func.__name__} is unsupported for objects of type: {type(obj).__name__}"
)
return key
if num_arrays < 0:
G_LOGGER.critical(
f"Function: {func.__name__} is unsupported with {num_arrays} < 0"
)
@functools.wraps(func)
def wrapped(*args, **kwargs):
if len(args) < num_arrays:
G_LOGGER.critical(
f"Function: {func.__name__} is unsupported for less than {num_arrays} positional arguments"
)
mapping = func()
obj0 = args[0]
key = _get_key(obj0)
if key not in mapping:
G_LOGGER.critical(
f"Function: {func.__name__} is unsupported for objects of type: {type(obj0).__name__}"
)
# Note that we can use to_torch/to_numpy here without a circular dependency because those functions
# take the num_arrays=1 path.
def convert_array(obj):
if key == "torch":
return to_torch(obj)
elif key == "numpy":
return to_numpy(obj)
else:
G_LOGGER.critical(
f"Function: {func.__name__} is unsupported for objects of type: {type(obj).__name__}"
)
converted_args = (
[obj0]
+ list(map(convert_array, args[1:num_arrays]))
+ list(args[num_arrays:])
)
return mapping[key](*converted_args, **kwargs)
return wrapped
return dispatch_impl
##
## Conversion Functions
##
@mod.export()
@dispatch()
def to_torch():
"""
Converts an array or tensor to a PyTorch tensor.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
torch.Tensor: The PyTorch tensor.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj,
"numpy": lambda obj: torch.from_numpy(obj),
"number": lambda obj: torch.tensor(obj),
}
@mod.export()
@dispatch()
def to_numpy():
"""
Converts an array or tensor to a NumPy array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
np.ndarray: The NumPy array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.numpy(force=True),
"numpy": lambda obj: obj,
"number": lambda obj: np.array(obj),
}
##
## Metadata
##
@mod.export()
@dispatch()
def nbytes():
"""
Calculate the number of bytes required by the input array.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
int: The number of bytes required by the array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.nelement() * obj.element_size(),
"numpy": lambda obj: obj.nbytes,
"device_view": lambda obj: obj.nbytes,
}
@mod.export()
@dispatch()
def size():
"""
Calculate the volume of the input array
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
int: The volume of the array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.numel(),
"numpy": lambda obj: obj.size,
}
@mod.export()
@dispatch()
def data_ptr():
"""
Return a pointer to the first element of the input array.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
int: A pointer to the first element of the array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.data_ptr(),
"numpy": lambda obj: obj.ctypes.data,
"device_view": lambda obj: obj.ptr,
}
@mod.export()
@dispatch()
def is_on_cpu():
"""
Returns whether the input array is in CPU memory.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
bool: Whether the array is in CPU, i.e. host, memory.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.device.type == "cpu",
"numpy": lambda _: True,
"device_view": lambda _: False,
}
@mod.export()
@dispatch()
def is_on_gpu():
"""
Returns whether the input array is in GPU memory.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
bool: Whether the array is in GPU, i.e. host, memory.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.device.type == "cuda",
"numpy": lambda _: False,
"device_view": lambda _: True,
}
@mod.export()
@dispatch()
def dtype():
"""
Return the data type the input array.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
DataType: The data type of the array
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
func = lambda obj: DataType.from_dtype(obj.dtype)
return {"torch": func, "numpy": func, "device_view": func}
@mod.export()
@dispatch()
def shape():
"""
Return the shape the input array.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray, DeviceView]: The shape of the array
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
func = lambda obj: obj.shape
return {"torch": func, "numpy": func, "device_view": func}
@mod.export()
def view(obj, dtype, shape):
"""
Return a view of the the input array with the given data type and shape.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]):
The array or tensor. Must be contiguous.
dtype (DataType): The data type to use for the view.
shape (Sequence[int]): The shape to use for the view.
Returns:
Union[torch.Tensor, numpy.ndarray, DeviceView]: The view of the array
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
if not is_contiguous(obj):
G_LOGGER.critical(f"Input array to view() must be contiguous in memory")
if is_device_view(obj):
return obj.view(shape=shape, dtype=dtype)
dtype = (
DataType.to_dtype(dtype, "numpy")
if is_numpy(obj)
else DataType.to_dtype(dtype, "torch")
)
return obj.reshape(-1).view(dtype).reshape(shape)
@mod.export()
@dispatch()
def is_contiguous():
"""
Checks whether the provided array is contiguous in memory.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
bool: Whether the array is contiguous in memory.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda obj: obj.is_contiguous(),
"numpy": lambda obj: obj.flags["C_CONTIGUOUS"],
"device_view": lambda _: True,
}
##
## Memory Management
##
@mod.export()
@dispatch()
def make_contiguous():
"""
Makes an array contiguous if it's not already.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceView]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray, DeviceView]: The contiguous array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def impl_numpy(obj):
if is_contiguous(obj):
return obj
return np.ascontiguousarray(obj)
return {
"torch": lambda obj: obj.contiguous(),
"numpy": impl_numpy,
"device_view": lambda obj: obj,
}
@mod.export()
@dispatch()
def resize_or_reallocate():
"""
Resizes the provided buffer, possibly reallocating the buffer.
Args:
obj (Union[torch.Tensor, numpy.ndarray, DeviceArray]): The array or tensor.
shape (Sequence[int]): The desired shape of the buffer.
Returns:
Union[torch.Tensor, numpy.ndarray, DeviceArray]: The resized buffer, possibly reallocated.
"""
def numpy_impl(obj, shape):
if shape != obj.shape:
try:
obj.resize(shape, refcheck=False)
except ValueError as err:
G_LOGGER.warning(
f"Could not resize NumPy array to shape: {shape}. "
f"Allocating a new array instead.\nNote: Error was: {err}"
)
obj = np.empty(shape, dtype=np.dtype(obj.dtype))
return obj
return {
"numpy": numpy_impl,
"torch": lambda obj, shape: obj.resize_(shape) if shape != obj.shape else obj,
"device_view": lambda obj, shape: (
obj.resize(shape) if shape != obj.shape else obj
),
}
##
## Math Helpers
##
@mod.export()
@dispatch()
def cast():
"""
Casts an array to the specified type.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
dtype (DataType): The type to cast to.
Returns:
Union[torch.Tensor, numpy.ndarray]: The casted array.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj, dtype: np.array(obj.astype(dtype.numpy())),
"torch": lambda obj, dtype: obj.to(DataType.to_dtype(dtype, "torch")),
}
@mod.export()
@dispatch()
def any():
"""
Return whether any of the values in the provided array evaluate to True.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
bool: Whether any of the values in the array evaluate to True.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.any(obj),
"torch": lambda obj: bool(torch.any(obj)),
}
@mod.export()
@dispatch()
def all():
"""
Return whether all of the values in the provided array evaluate to True.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
bool: Whether all of the values in the array evaluate to True.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.all(obj),
"torch": lambda obj: bool(torch.all(obj)),
}
@mod.export()
@dispatch(num_arrays=2)
def equal():
"""
Returns whether two arrays are equal
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
bool: Whether the arrays are equal.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda lhs, rhs: torch.equal(lhs, rhs),
"numpy": lambda lhs, rhs: np.array_equal(lhs, rhs),
}
@mod.export()
@dispatch(num_arrays=2)
def subtract():
"""
Subtracts the second array from the first.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The difference.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda lhs, rhs: lhs - rhs,
"numpy": lambda lhs, rhs: np.array(lhs - rhs),
}
@mod.export()
@dispatch(num_arrays=2)
def divide():
"""
Divides the first array by the second.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The quotient.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"torch": lambda lhs, rhs: lhs / rhs,
"numpy": lambda lhs, rhs: lhs / rhs,
}
@mod.export()
@dispatch(num_arrays=2)
def allclose():
"""
Returns whether all the values in two arrays are within the given thresholds.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
rtol (float): The relative tolerance. Defaults to 1e-5.
atol (float): The absolute tolerance. Defaults to 1e-8.
Returns:
bool: Whether the arrays are close.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
DEFAULT_RTOL = 1e-5
DEFAULT_ATOL = 1e-8
return {
"torch": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: torch.allclose(
lhs, rhs, rtol=rtol, atol=atol
),
"numpy": lambda lhs, rhs, rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL: np.allclose(
lhs, rhs, rtol=rtol, atol=atol
),
}
@mod.export()
def unravel_index(index, shape):
"""
Unravels a flat index into a N-dimensional index based on the specified shape.
Args:
index (int): The flat index.
shape (Sequence[int]): The shape on which to unravel the index.
Returns:
Tuple[int]: The N-dimensional index.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
index = int(index)
nd_index = []
for dim in reversed(shape):
nd_index.insert(0, index % dim)
index = index // dim
return tuple(nd_index)
@mod.export()
@dispatch()
def histogram():
"""
Compute a histogram for the given array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
range (Tuple[float, float]): The lower and upper range of the bins.
Returns:
Tuple[Union[torch.Tensor, numpy.ndarray], Union[torch.Tensor, numpy.ndarray]]:
The histogram values and the bin edges
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj, range=None):
# PyTorch doesn't support histograms for all types, so cast to FP32
original_dtype = obj.dtype
hist, bins = torch.histogram(obj.to(torch.float32), bins=10, range=range)
return hist.to(original_dtype), bins.to(original_dtype)
return {
"numpy": lambda obj, range=None: np.histogram(obj, bins=10, range=range),
"torch": torch_impl,
}
@mod.export()
@dispatch()
def max():
"""
Returns the maximum value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The maximum value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.amax(obj).item(),
"torch": lambda obj: torch.max(obj).item(),
}
@mod.export()
@dispatch()
def argmax():
"""
Returns the flattened index of the maximum value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
int: The flattened index.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj):
# Torch argmax doesn't support bools
return torch.argmax(obj.to(torch.float32))
return {
"numpy": lambda obj: np.argmax(obj),
"torch": lambda obj: torch_impl(obj),
}
@mod.export()
@dispatch()
def min():
"""
Returns the minimum value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The minimum value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.amin(obj).item(),
"torch": lambda obj: torch.min(obj).item(),
}
@mod.export()
@dispatch()
def argmin():
"""
Returns the flattened index of the minimum value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
int: The flattened index.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj):
# Torch argmin doesn't support bools
return torch.argmin(obj.to(torch.float32))
return {
"numpy": lambda obj: np.argmin(obj),
"torch": lambda obj: torch_impl(obj),
}
@mod.export()
@dispatch()
def mean():
"""
Returns the mean value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
dtype (DataType): The mean compute type.
Returns:
Any: The mean value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj, dtype=None: np.mean(
obj, dtype=DataType.to_dtype(dtype, "numpy") if dtype is not None else None
),
"torch": lambda obj, dtype=None: torch.mean(
obj, dtype=DataType.to_dtype(dtype, "torch") if dtype is not None else None
),
}
@mod.export()
@dispatch()
def std():
"""
Returns the standard deviation of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The standard deviation
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj):
# torch.var is only supported for floats, so cast up and then back.
obj_fp32 = obj.to(torch.float32)
try:
return torch.std(obj_fp32, correction=0)
except AttributeError:
return torch.std(obj_fp32, unbiased=False)
return {
"numpy": lambda obj: np.std(obj),
"torch": torch_impl,
}
@mod.export()
@dispatch()
def var():
"""
Returns the variance of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The variance
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj):
# torch.var is only supported for floats, so cast up and then back.
obj_fp32 = obj.to(torch.float32)
try:
return torch.var(obj_fp32, correction=0)
except AttributeError:
return torch.var(obj_fp32, unbiased=False)
return {
"numpy": lambda obj: np.var(obj),
"torch": torch_impl,
}
@mod.export()
@dispatch()
def median():
"""
Returns the median value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The median value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_impl(obj):
# Median in PyTorch doesn't work as expected for arrays with an even number of elements - instead
# of returning the average of the two middle elements, it just returns the smaller one.
# It is also not implemented for some types, so cast to FP32 for compute.
original_dtype = obj.dtype
obj = obj.to(torch.float32)
rv = 0
if obj.nelement() % 2 == 1:
rv = torch.median(obj)
else:
smaller = torch.median(obj)
larger = torch.median(torch.cat([obj.flatten(), torch.max(obj)[None]]))
rv = (smaller + larger) / 2.0
return rv.to(original_dtype)
return {
"numpy": lambda obj: np.median(obj),
"torch": torch_impl,
}
@mod.export()
@dispatch()
def quantile():
"""
Returns the value of the q quantile of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
q (float): Quantile to compute, expected range [0, 1]
Returns:
Any: The quantile value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def numpy_impl(obj, q):
if obj.size == 0:
return np.inf
return np.quantile(obj, q)
def torch_impl(obj, q):
if obj.numel() == 0:
return torch.inf
original_dtype = obj.dtype
obj = obj.to(torch.float32)
qunatile_val = torch.quantile(obj, q)
return qunatile_val.to(original_dtype)
return {
"numpy": numpy_impl,
"torch": torch_impl,
}
@mod.export()
@dispatch()
def topk():
"""
Returns a tuple of the top k values and indices of an array along a specified axis.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
k (int): The number of values to return. This is clamped to the length of obj along the given axis.
axis (int): The axis to perform the topk computation on
Returns:
Tuple[Union[torch.Tensor, numpy.ndarray], Union[torch.Tensor, numpy.ndarray]]: A tuple containing a pair of arrays,
the first being the values and the second being the indices of the top k values along the specified axis
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def numpy_impl(obj, k, axis):
# NumPy doesn't have a Top K implementation
indices = np.argsort(-obj, axis=axis, kind="stable")
axis_len = indices.shape[axis]
indices = np.take(indices, np.arange(0, builtins.min(k, axis_len)), axis=axis)
return np.take_along_axis(obj, indices, axis=axis), indices
def torch_impl(obj, k, axis):
axis_len = obj.shape[axis]
# Top K has no implementation for float16 in torch-cpu, so
# If gpu is available, run computation there
# Otherwise, run the calculation on cpu using fp32 precision
if obj.dtype == torch.float16:
if torch.cuda.is_available():
original_device = obj.device
ret = tuple(
torch.topk(obj.to("cuda"), builtins.min(k, axis_len), dim=axis)
)
return (ret[0].to(original_device), ret[1].to(original_device))
else:
ret = tuple(
torch.topk(
obj.type(torch.float32), builtins.min(k, axis_len), dim=axis
)
)
return (ret[0].type(torch.float16), ret[1].type(torch.float16))
return tuple(torch.topk(obj, builtins.min(k, axis_len), dim=axis))
return {
"numpy": numpy_impl,
"torch": torch_impl,
}
@mod.export()
@dispatch()
def abs():
"""
Returns the absolute value of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Any: The absolute value
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
def torch_abs_impl(obj):
# PyTorch doesn't support abs for all types, so cast to FP32
original_dtype = obj.dtype
return torch.abs(obj.to(torch.float32)).to(original_dtype)
return {
"numpy": lambda obj: np.array(np.abs(obj)),
"torch": lambda obj: torch_abs_impl(obj),
}
@mod.export()
@dispatch()
def isfinite():
"""
Returns a boolean array indicating if each element of obj is finite or not.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The boolean array indicating which elements of obj are finite.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.isfinite(obj),
"torch": lambda obj: torch.isfinite(obj),
}
@mod.export()
@dispatch()
def isinf():
"""
Returns a boolean array indicating if each element of obj is infinite or not.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The boolean array indicating which elements of obj are infinite.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.isinf(obj),
"torch": lambda obj: torch.isinf(obj),
}
@mod.export()
@dispatch()
def isnan():
"""
Returns a boolean array indicating if each element of obj is NaN or not.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The boolean array indicating which elements of obj are NaN.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.isnan(obj),
"torch": lambda obj: torch.isnan(obj),
"number": lambda obj: math.isnan(obj),
}
@mod.export()
@dispatch()
def argwhere():
"""
Returns a indices of non-zero array elements
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: An (N, obj.ndim) array containing indices of non-zero elements of obj
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.argwhere(obj),
"torch": lambda obj: torch.argwhere(obj),
}
@mod.export()
@dispatch()
def ravel():
"""
Flattens the input array
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The flattened input tensor
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.ravel(obj),
"torch": lambda obj: torch.ravel(obj),
}
@mod.export()
@dispatch()
def logical_not():
"""
Computes the logical not of an array
Args:
obj (Union[torch.Tensor, numpy.ndarray]):
The input array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The logical not.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.logical_not(obj),
"torch": lambda obj: torch.logical_not(obj),
}
@mod.export()
@dispatch(num_arrays=2)
def logical_xor():
"""
Computes the logical exclusive-or of two arrays.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The logical xor.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda lhs, rhs: np.logical_xor(lhs, rhs),
"torch": lambda lhs, rhs: torch.logical_xor(lhs, rhs),
}
@mod.export()
@dispatch(num_arrays=2)
def logical_and():
"""
Computes the logical and of two arrays.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The logical and.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda lhs, rhs: np.logical_and(lhs, rhs),
"torch": lambda lhs, rhs: torch.logical_and(lhs, rhs),
}
@mod.export()
@dispatch(num_arrays=2)
def greater():
"""
Returns a boolean array indicating where lhs is greater than rhs
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: Boolean array indicating whether lhs > rhs.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda lhs, rhs: np.greater(lhs, rhs),
"torch": lambda lhs, rhs: torch.gt(lhs, rhs),
}
@mod.export()
@dispatch(num_arrays=3)
def where():
"""
Returns an array containing elements from lhs when cond is true, and rhs when cond is false.
Computes the logical and of two arrays.
Args:
cond (Union[torch.Tensor, numpy.ndarray]):
The condition array or tensor.
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: Selected elements from lhs if cond is true, and rhs otherwise
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda cond, lhs, rhs: np.where(cond, lhs, rhs),
"torch": lambda cond, lhs, rhs: torch.where(cond, lhs, rhs),
}
@mod.export()
@dispatch(num_arrays=2)
def power():
"""
Computes the element-wise power of an array to the given exponent.
Args:
obj (Union[torch.Tensor, numpy.ndarray]):
The base array or tensor.
exponent (Union[int, float, torch.Tensor, numpy.ndarray]):
The exponent value or array.
Returns:
Union[torch.Tensor, numpy.ndarray]: The power result.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj, exponent: np.power(obj, exponent),
"torch": lambda obj, exponent: torch.pow(obj, exponent),
}
@mod.export()
@dispatch()
def sum():
"""
Computes the sum of all elements in the array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[Number, torch.Tensor, numpy.ndarray]: The sum of all elements.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.sum(obj),
"torch": lambda obj: torch.sum(obj),
}
@mod.export()
@dispatch()
def sqrt():
"""
Computes the element-wise square root of an array.
Args:
obj (Union[torch.Tensor, numpy.ndarray]): The array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The square root results.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda obj: np.sqrt(obj),
"torch": lambda obj: torch.sqrt(obj),
}
@mod.export()
@dispatch(num_arrays=2)
def multiply():
"""
Computes the element-wise multiplication of two arrays.
Args:
lhs (Union[torch.Tensor, numpy.ndarray]):
The first array or tensor.
rhs (Union[torch.Tensor, numpy.ndarray]):
The second array or tensor.
Returns:
Union[torch.Tensor, numpy.ndarray]: The element-wise product.
Raises:
PolygraphyException: if the input is of an unrecognized type.
"""
return {
"numpy": lambda lhs, rhs: np.multiply(lhs, rhs),
"torch": lambda lhs, rhs: torch.mul(lhs, rhs),
}