Kernels:
Trusted publisher
Uploaded using `kernel-builder`.
Browse files- build/torch-cuda/__init__.py +18 -0
- build/torch-cuda/_backends.py +719 -0
- build/torch-cuda/_ops.py +38 -0
- build/torch-cuda/_torch_specific.py +128 -0
- build/torch-cuda/array_api.py +124 -0
- build/torch-cuda/einops.py +916 -0
- build/torch-cuda/experimental/__init__.py +0 -0
- build/torch-cuda/experimental/indexing.py +5 -0
- build/torch-cuda/layers/__init__.py +106 -0
- build/torch-cuda/layers/_einmix.py +229 -0
- build/torch-cuda/layers/flax.py +82 -0
- build/torch-cuda/layers/keras.py +9 -0
- build/torch-cuda/layers/oneflow.py +54 -0
- build/torch-cuda/layers/paddle.py +58 -0
- build/torch-cuda/layers/tensorflow.py +103 -0
- build/torch-cuda/layers/torch.py +67 -0
- build/torch-cuda/metadata.json +45 -0
- build/torch-cuda/metadata.json.sigstore +1 -0
- build/torch-cuda/packing.py +190 -0
- build/torch-cuda/parsing.py +152 -0
build/torch-cuda/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# imports can use EinopsError class
|
| 2 |
+
# ruff: noqa: E402
|
| 3 |
+
|
| 4 |
+
__author__ = "Alex Rogozhnikov"
|
| 5 |
+
__version__ = "0.8.1"
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class EinopsError(RuntimeError):
|
| 9 |
+
"""Runtime error thrown by einops"""
|
| 10 |
+
|
| 11 |
+
pass
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
__all__ = ["rearrange", "reduce", "repeat", "einsum", "pack", "unpack", "parse_shape", "asnumpy", "EinopsError"]
|
| 15 |
+
|
| 16 |
+
from .einops import rearrange, reduce, repeat, einsum, parse_shape, asnumpy
|
| 17 |
+
from .packing import pack, unpack
|
| 18 |
+
from . import array_api
|
build/torch-cuda/_backends.py
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Backends in `einops` are organized to meet the following requirements
|
| 3 |
+
- backends are not imported unless those are actually needed, because
|
| 4 |
+
- backends may not be installed
|
| 5 |
+
- importing all available backends will drive to significant memory footprint
|
| 6 |
+
- backends may be present but installed with errors (but never used),
|
| 7 |
+
importing may drive to crashes
|
| 8 |
+
- backend should be either symbolic or imperative
|
| 9 |
+
- this determines which methods (from_numpy/to_numpy or create_symbol/eval_symbol) should be defined
|
| 10 |
+
- if backend can't provide symbols for shape dimensions, UnknownSize objects are used
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
__author__ = "Alex Rogozhnikov"
|
| 16 |
+
|
| 17 |
+
_loaded_backends: dict = {}
|
| 18 |
+
_type2backend: dict = {}
|
| 19 |
+
_debug_importing = False
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_backend(tensor) -> "AbstractBackend":
|
| 23 |
+
"""
|
| 24 |
+
Takes a correct backend (e.g. numpy backend if tensor is numpy.ndarray) for a tensor.
|
| 25 |
+
If needed, imports package and creates backend
|
| 26 |
+
"""
|
| 27 |
+
_type = type(tensor)
|
| 28 |
+
_result = _type2backend.get(_type, None)
|
| 29 |
+
if _result is not None:
|
| 30 |
+
return _result
|
| 31 |
+
|
| 32 |
+
for framework_name, backend in list(_loaded_backends.items()):
|
| 33 |
+
if backend.is_appropriate_type(tensor):
|
| 34 |
+
_type2backend[_type] = backend
|
| 35 |
+
return backend
|
| 36 |
+
|
| 37 |
+
# Find backend subclasses recursively
|
| 38 |
+
backend_subclasses = []
|
| 39 |
+
backends = AbstractBackend.__subclasses__()
|
| 40 |
+
while backends:
|
| 41 |
+
backend = backends.pop()
|
| 42 |
+
backends += backend.__subclasses__()
|
| 43 |
+
backend_subclasses.append(backend)
|
| 44 |
+
|
| 45 |
+
for BackendSubclass in backend_subclasses:
|
| 46 |
+
if _debug_importing:
|
| 47 |
+
print("Testing for subclass of ", BackendSubclass)
|
| 48 |
+
if BackendSubclass.framework_name not in _loaded_backends:
|
| 49 |
+
# check that module was already imported. Otherwise it can't be imported
|
| 50 |
+
if BackendSubclass.framework_name in sys.modules:
|
| 51 |
+
if _debug_importing:
|
| 52 |
+
print("Imported backend for ", BackendSubclass.framework_name)
|
| 53 |
+
backend = BackendSubclass()
|
| 54 |
+
_loaded_backends[backend.framework_name] = backend
|
| 55 |
+
if backend.is_appropriate_type(tensor):
|
| 56 |
+
_type2backend[_type] = backend
|
| 57 |
+
return backend
|
| 58 |
+
|
| 59 |
+
raise RuntimeError("Tensor type unknown to einops {}".format(type(tensor)))
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class AbstractBackend:
|
| 63 |
+
"""Base backend class, major part of methods are only for debugging purposes."""
|
| 64 |
+
|
| 65 |
+
framework_name: str
|
| 66 |
+
|
| 67 |
+
def is_appropriate_type(self, tensor):
|
| 68 |
+
"""helper method should recognize tensors it can handle"""
|
| 69 |
+
raise NotImplementedError()
|
| 70 |
+
|
| 71 |
+
def from_numpy(self, x):
|
| 72 |
+
raise NotImplementedError("framework doesn't support imperative execution")
|
| 73 |
+
|
| 74 |
+
def to_numpy(self, x):
|
| 75 |
+
raise NotImplementedError("framework doesn't support imperative execution")
|
| 76 |
+
|
| 77 |
+
def create_symbol(self, shape):
|
| 78 |
+
raise NotImplementedError("framework doesn't support symbolic computations")
|
| 79 |
+
|
| 80 |
+
def eval_symbol(self, symbol, symbol_value_pairs):
|
| 81 |
+
# symbol-value pairs is list[tuple[symbol, value-tensor]]
|
| 82 |
+
raise NotImplementedError("framework doesn't support symbolic computations")
|
| 83 |
+
|
| 84 |
+
def arange(self, start, stop):
|
| 85 |
+
# supplementary method used only in testing, so should implement CPU version
|
| 86 |
+
raise NotImplementedError("framework doesn't implement arange")
|
| 87 |
+
|
| 88 |
+
def shape(self, x):
|
| 89 |
+
"""shape should return a tuple with integers or "shape symbols" (which will evaluate to actual size)"""
|
| 90 |
+
return x.shape
|
| 91 |
+
|
| 92 |
+
def reshape(self, x, shape):
|
| 93 |
+
return x.reshape(shape)
|
| 94 |
+
|
| 95 |
+
def transpose(self, x, axes):
|
| 96 |
+
return x.transpose(axes)
|
| 97 |
+
|
| 98 |
+
def reduce(self, x, operation, axes):
|
| 99 |
+
return getattr(x, operation)(axis=axes)
|
| 100 |
+
|
| 101 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 102 |
+
raise NotImplementedError()
|
| 103 |
+
|
| 104 |
+
def add_axis(self, x, new_position):
|
| 105 |
+
raise NotImplementedError()
|
| 106 |
+
|
| 107 |
+
def add_axes(self, x, n_axes, pos2len):
|
| 108 |
+
repeats = [1] * n_axes
|
| 109 |
+
for axis_position, axis_length in pos2len.items():
|
| 110 |
+
x = self.add_axis(x, axis_position)
|
| 111 |
+
repeats[axis_position] = axis_length
|
| 112 |
+
return self.tile(x, tuple(repeats))
|
| 113 |
+
|
| 114 |
+
def tile(self, x, repeats):
|
| 115 |
+
"""repeats - same lengths as x.shape"""
|
| 116 |
+
raise NotImplementedError()
|
| 117 |
+
|
| 118 |
+
def concat(self, tensors, axis: int):
|
| 119 |
+
"""concatenates tensors along axis.
|
| 120 |
+
Assume identical across tensors: devices, dtypes and shapes except selected axis."""
|
| 121 |
+
raise NotImplementedError()
|
| 122 |
+
|
| 123 |
+
def is_float_type(self, x):
|
| 124 |
+
# some backends (torch) can't compute average for non-floating types.
|
| 125 |
+
# Decided to drop average for all backends if type is not floating
|
| 126 |
+
raise NotImplementedError()
|
| 127 |
+
|
| 128 |
+
def layers(self):
|
| 129 |
+
raise NotImplementedError("backend does not provide layers")
|
| 130 |
+
|
| 131 |
+
def __repr__(self):
|
| 132 |
+
return "<einops backend for {}>".format(self.framework_name)
|
| 133 |
+
|
| 134 |
+
def einsum(self, pattern, *x):
|
| 135 |
+
raise NotImplementedError("backend does not support einsum")
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class UnknownSize:
|
| 139 |
+
"""pseudo-symbol for symbolic frameworks which do not provide symbols for shape elements"""
|
| 140 |
+
|
| 141 |
+
def __floordiv__(self, other):
|
| 142 |
+
return self
|
| 143 |
+
|
| 144 |
+
def __eq__(self, other):
|
| 145 |
+
return True # we don't know actual size
|
| 146 |
+
|
| 147 |
+
def __mul__(self, other):
|
| 148 |
+
return self
|
| 149 |
+
|
| 150 |
+
def __rmul__(self, other):
|
| 151 |
+
return self
|
| 152 |
+
|
| 153 |
+
def __hash__(self):
|
| 154 |
+
return hash(None)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class NumpyBackend(AbstractBackend):
|
| 158 |
+
framework_name = "numpy"
|
| 159 |
+
|
| 160 |
+
def __init__(self):
|
| 161 |
+
import numpy
|
| 162 |
+
|
| 163 |
+
self.np = numpy
|
| 164 |
+
|
| 165 |
+
def is_appropriate_type(self, tensor):
|
| 166 |
+
return isinstance(tensor, self.np.ndarray)
|
| 167 |
+
|
| 168 |
+
def from_numpy(self, x):
|
| 169 |
+
return x
|
| 170 |
+
|
| 171 |
+
def to_numpy(self, x):
|
| 172 |
+
return x
|
| 173 |
+
|
| 174 |
+
def arange(self, start, stop):
|
| 175 |
+
return self.np.arange(start, stop)
|
| 176 |
+
|
| 177 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 178 |
+
return self.np.stack(tensors)
|
| 179 |
+
|
| 180 |
+
def tile(self, x, repeats):
|
| 181 |
+
return self.np.tile(x, repeats)
|
| 182 |
+
|
| 183 |
+
def concat(self, tensors, axis: int):
|
| 184 |
+
return self.np.concatenate(tensors, axis=axis)
|
| 185 |
+
|
| 186 |
+
def is_float_type(self, x):
|
| 187 |
+
return x.dtype in ("float16", "float32", "float64", "float128", "bfloat16")
|
| 188 |
+
|
| 189 |
+
def add_axis(self, x, new_position):
|
| 190 |
+
return self.np.expand_dims(x, new_position)
|
| 191 |
+
|
| 192 |
+
def einsum(self, pattern, *x):
|
| 193 |
+
return self.np.einsum(pattern, *x)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
class JaxBackend(NumpyBackend):
|
| 197 |
+
framework_name = "jax"
|
| 198 |
+
|
| 199 |
+
def __init__(self):
|
| 200 |
+
super(JaxBackend, self).__init__()
|
| 201 |
+
self.onp = self.np
|
| 202 |
+
|
| 203 |
+
import jax.numpy
|
| 204 |
+
|
| 205 |
+
self.np = jax.numpy
|
| 206 |
+
|
| 207 |
+
def from_numpy(self, x):
|
| 208 |
+
return self.np.asarray(x)
|
| 209 |
+
|
| 210 |
+
def to_numpy(self, x):
|
| 211 |
+
return self.onp.asarray(x)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class TorchBackend(AbstractBackend):
|
| 215 |
+
framework_name = "torch"
|
| 216 |
+
|
| 217 |
+
def __init__(self):
|
| 218 |
+
import torch
|
| 219 |
+
|
| 220 |
+
self.torch = torch
|
| 221 |
+
# importing would register operations in torch._dynamo for torch.compile
|
| 222 |
+
from . import _torch_specific # noqa
|
| 223 |
+
|
| 224 |
+
def is_appropriate_type(self, tensor):
|
| 225 |
+
return isinstance(tensor, self.torch.Tensor)
|
| 226 |
+
|
| 227 |
+
def from_numpy(self, x):
|
| 228 |
+
variable = self.torch.from_numpy(x)
|
| 229 |
+
if self.is_float_type(variable):
|
| 230 |
+
# attach grad only to floating types
|
| 231 |
+
variable.requires_grad = True
|
| 232 |
+
return variable
|
| 233 |
+
|
| 234 |
+
def to_numpy(self, x):
|
| 235 |
+
return x.detach().cpu().numpy()
|
| 236 |
+
|
| 237 |
+
def arange(self, start, stop):
|
| 238 |
+
return self.torch.arange(start, stop, dtype=self.torch.int64)
|
| 239 |
+
|
| 240 |
+
def reduce(self, x, operation, reduced_axes):
|
| 241 |
+
if operation == "min":
|
| 242 |
+
return x.amin(dim=reduced_axes)
|
| 243 |
+
elif operation == "max":
|
| 244 |
+
return x.amax(dim=reduced_axes)
|
| 245 |
+
elif operation == "sum":
|
| 246 |
+
return x.sum(dim=reduced_axes)
|
| 247 |
+
elif operation == "mean":
|
| 248 |
+
return x.mean(dim=reduced_axes)
|
| 249 |
+
elif operation in ("any", "all", "prod"):
|
| 250 |
+
# pytorch supports reducing only one operation at a time
|
| 251 |
+
for i in list(sorted(reduced_axes))[::-1]:
|
| 252 |
+
x = getattr(x, operation)(dim=i)
|
| 253 |
+
return x
|
| 254 |
+
else:
|
| 255 |
+
raise NotImplementedError("Unknown reduction ", operation)
|
| 256 |
+
|
| 257 |
+
def transpose(self, x, axes):
|
| 258 |
+
return x.permute(axes)
|
| 259 |
+
|
| 260 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 261 |
+
return self.torch.stack(tensors)
|
| 262 |
+
|
| 263 |
+
def add_axes(self, x, n_axes, pos2len):
|
| 264 |
+
repeats = [-1] * n_axes
|
| 265 |
+
for axis_position, axis_length in pos2len.items():
|
| 266 |
+
x = self.add_axis(x, axis_position)
|
| 267 |
+
repeats[axis_position] = axis_length
|
| 268 |
+
return x.expand(repeats)
|
| 269 |
+
|
| 270 |
+
def tile(self, x, repeats):
|
| 271 |
+
return x.repeat(repeats)
|
| 272 |
+
|
| 273 |
+
def concat(self, tensors, axis: int):
|
| 274 |
+
return self.torch.cat(tensors, dim=axis)
|
| 275 |
+
|
| 276 |
+
def add_axis(self, x, new_position):
|
| 277 |
+
return self.torch.unsqueeze(x, new_position)
|
| 278 |
+
|
| 279 |
+
def is_float_type(self, x):
|
| 280 |
+
return x.dtype in [self.torch.float16, self.torch.float32, self.torch.float64, self.torch.bfloat16]
|
| 281 |
+
|
| 282 |
+
def layers(self):
|
| 283 |
+
from .layers import torch
|
| 284 |
+
|
| 285 |
+
return torch
|
| 286 |
+
|
| 287 |
+
def einsum(self, pattern, *x):
|
| 288 |
+
return self.torch.einsum(pattern, *x)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class CupyBackend(AbstractBackend):
|
| 292 |
+
framework_name = "cupy"
|
| 293 |
+
|
| 294 |
+
def __init__(self):
|
| 295 |
+
import cupy
|
| 296 |
+
|
| 297 |
+
self.cupy = cupy
|
| 298 |
+
|
| 299 |
+
def is_appropriate_type(self, tensor):
|
| 300 |
+
return isinstance(tensor, self.cupy.ndarray)
|
| 301 |
+
|
| 302 |
+
def from_numpy(self, x):
|
| 303 |
+
return self.cupy.asarray(x)
|
| 304 |
+
|
| 305 |
+
def to_numpy(self, x):
|
| 306 |
+
return self.cupy.asnumpy(x)
|
| 307 |
+
|
| 308 |
+
def arange(self, start, stop):
|
| 309 |
+
return self.cupy.arange(start, stop)
|
| 310 |
+
|
| 311 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 312 |
+
return self.cupy.stack(tensors)
|
| 313 |
+
|
| 314 |
+
def tile(self, x, repeats):
|
| 315 |
+
return self.cupy.tile(x, repeats)
|
| 316 |
+
|
| 317 |
+
def concat(self, tensors, axis: int):
|
| 318 |
+
return self.cupy.concatenate(tensors, axis=axis)
|
| 319 |
+
|
| 320 |
+
def add_axis(self, x, new_position):
|
| 321 |
+
return self.cupy.expand_dims(x, new_position)
|
| 322 |
+
|
| 323 |
+
def is_float_type(self, x):
|
| 324 |
+
return x.dtype in ("float16", "float32", "float64", "float128", "bfloat16")
|
| 325 |
+
|
| 326 |
+
def einsum(self, pattern, *x):
|
| 327 |
+
return self.cupy.einsum(pattern, *x)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class HashableTuple:
|
| 331 |
+
"""Overcomes non-hashability of symbolic elements"""
|
| 332 |
+
|
| 333 |
+
def __init__(self, elements: tuple):
|
| 334 |
+
self.elements = elements
|
| 335 |
+
|
| 336 |
+
def __iter__(self):
|
| 337 |
+
for x in self.elements:
|
| 338 |
+
yield x
|
| 339 |
+
|
| 340 |
+
def __len__(self):
|
| 341 |
+
return len(self.elements)
|
| 342 |
+
|
| 343 |
+
def __getitem__(self, item):
|
| 344 |
+
return self.elements[item]
|
| 345 |
+
|
| 346 |
+
# default equality and hash is used (True only with itself, hash taken of id)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
class TensorflowBackend(AbstractBackend):
|
| 350 |
+
framework_name = "tensorflow"
|
| 351 |
+
|
| 352 |
+
def __init__(self):
|
| 353 |
+
import tensorflow
|
| 354 |
+
|
| 355 |
+
self.tf = tensorflow
|
| 356 |
+
|
| 357 |
+
def is_appropriate_type(self, tensor):
|
| 358 |
+
return isinstance(tensor, (self.tf.Tensor, self.tf.Variable))
|
| 359 |
+
|
| 360 |
+
def from_numpy(self, x):
|
| 361 |
+
assert self.tf.executing_eagerly()
|
| 362 |
+
return self.tf.convert_to_tensor(x)
|
| 363 |
+
|
| 364 |
+
def to_numpy(self, x):
|
| 365 |
+
assert self.tf.executing_eagerly()
|
| 366 |
+
return x.numpy()
|
| 367 |
+
|
| 368 |
+
def arange(self, start, stop):
|
| 369 |
+
return self.tf.range(start, stop)
|
| 370 |
+
|
| 371 |
+
def shape(self, x):
|
| 372 |
+
if self.tf.executing_eagerly():
|
| 373 |
+
return tuple(UnknownSize() if d is None else int(d) for d in x.shape)
|
| 374 |
+
else:
|
| 375 |
+
static_shape = x.shape.as_list()
|
| 376 |
+
tf_shape = self.tf.shape(x)
|
| 377 |
+
# use the static shape where known, otherwise use the TF shape components
|
| 378 |
+
shape = tuple([s or tf_shape[dim] for dim, s in enumerate(static_shape)])
|
| 379 |
+
try:
|
| 380 |
+
hash(shape)
|
| 381 |
+
return shape
|
| 382 |
+
except BaseException:
|
| 383 |
+
# unhashable symbols in shape. Wrap tuple to be hashable.
|
| 384 |
+
return HashableTuple(shape)
|
| 385 |
+
|
| 386 |
+
def reduce(self, x, operation, axes):
|
| 387 |
+
return getattr(self.tf, "reduce_" + operation)(x, axis=axes)
|
| 388 |
+
|
| 389 |
+
def reshape(self, x, shape):
|
| 390 |
+
return self.tf.reshape(x, shape)
|
| 391 |
+
|
| 392 |
+
def transpose(self, x, axes):
|
| 393 |
+
return self.tf.transpose(x, axes)
|
| 394 |
+
|
| 395 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 396 |
+
return self.tf.stack(tensors)
|
| 397 |
+
|
| 398 |
+
def tile(self, x, repeats):
|
| 399 |
+
return self.tf.tile(x, repeats)
|
| 400 |
+
|
| 401 |
+
def concat(self, tensors, axis: int):
|
| 402 |
+
return self.tf.concat(tensors, axis=axis)
|
| 403 |
+
|
| 404 |
+
def add_axis(self, x, new_position):
|
| 405 |
+
return self.tf.expand_dims(x, new_position)
|
| 406 |
+
|
| 407 |
+
def is_float_type(self, x):
|
| 408 |
+
return x.dtype in ("float16", "float32", "float64", "float128", "bfloat16")
|
| 409 |
+
|
| 410 |
+
def layers(self):
|
| 411 |
+
from .layers import tensorflow
|
| 412 |
+
|
| 413 |
+
return tensorflow
|
| 414 |
+
|
| 415 |
+
def einsum(self, pattern, *x):
|
| 416 |
+
return self.tf.einsum(pattern, *x)
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class TFKerasBackend(AbstractBackend):
|
| 420 |
+
framework_name = "tensorflow.keras"
|
| 421 |
+
|
| 422 |
+
def __init__(self):
|
| 423 |
+
import tensorflow as tf
|
| 424 |
+
|
| 425 |
+
self.tf = tf
|
| 426 |
+
self.keras = tf.keras
|
| 427 |
+
self.K = tf.keras.backend
|
| 428 |
+
|
| 429 |
+
def is_appropriate_type(self, tensor):
|
| 430 |
+
return self.tf.is_tensor(tensor) and self.K.is_keras_tensor(tensor)
|
| 431 |
+
|
| 432 |
+
def create_symbol(self, shape):
|
| 433 |
+
return self.keras.Input(batch_shape=shape)
|
| 434 |
+
|
| 435 |
+
def eval_symbol(self, symbol, symbol_value_pairs):
|
| 436 |
+
model = self.keras.models.Model([var for (var, _) in symbol_value_pairs], symbol)
|
| 437 |
+
return model.predict_on_batch([val for (_, val) in symbol_value_pairs])
|
| 438 |
+
|
| 439 |
+
def arange(self, start, stop):
|
| 440 |
+
return self.K.arange(start, stop)
|
| 441 |
+
|
| 442 |
+
def shape(self, x):
|
| 443 |
+
shape = self.K.shape(x) # tf tensor
|
| 444 |
+
return HashableTuple(tuple(shape))
|
| 445 |
+
|
| 446 |
+
def reduce(self, x, operation, axes):
|
| 447 |
+
return getattr(self.K, operation)(x, axis=axes)
|
| 448 |
+
|
| 449 |
+
def reshape(self, x, shape):
|
| 450 |
+
return self.K.reshape(x, shape)
|
| 451 |
+
|
| 452 |
+
def transpose(self, x, axes):
|
| 453 |
+
return self.K.permute_dimensions(x, axes)
|
| 454 |
+
|
| 455 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 456 |
+
return self.K.stack(tensors)
|
| 457 |
+
|
| 458 |
+
def tile(self, x, repeats):
|
| 459 |
+
return self.K.tile(x, repeats)
|
| 460 |
+
|
| 461 |
+
def concat(self, tensors, axis: int):
|
| 462 |
+
return self.K.concatenate(tensors, axis=axis)
|
| 463 |
+
|
| 464 |
+
def add_axis(self, x, new_position):
|
| 465 |
+
return self.K.expand_dims(x, new_position)
|
| 466 |
+
|
| 467 |
+
def is_float_type(self, x):
|
| 468 |
+
return "float" in self.K.dtype(x)
|
| 469 |
+
|
| 470 |
+
def layers(self):
|
| 471 |
+
from .layers import keras
|
| 472 |
+
|
| 473 |
+
return keras
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
class OneFlowBackend(AbstractBackend):
|
| 477 |
+
framework_name = "oneflow"
|
| 478 |
+
|
| 479 |
+
def __init__(self):
|
| 480 |
+
import oneflow as flow
|
| 481 |
+
|
| 482 |
+
self.flow = flow
|
| 483 |
+
|
| 484 |
+
def is_appropriate_type(self, tensor):
|
| 485 |
+
return isinstance(tensor, self.flow.Tensor)
|
| 486 |
+
|
| 487 |
+
def from_numpy(self, x):
|
| 488 |
+
variable = self.flow.from_numpy(x)
|
| 489 |
+
if self.is_float_type(variable):
|
| 490 |
+
# attach grad only to floating types
|
| 491 |
+
variable.requires_grad = True
|
| 492 |
+
return variable
|
| 493 |
+
|
| 494 |
+
def to_numpy(self, x):
|
| 495 |
+
return x.detach().cpu().numpy()
|
| 496 |
+
|
| 497 |
+
def arange(self, start, stop):
|
| 498 |
+
return self.flow.arange(start, stop, dtype=self.flow.int64)
|
| 499 |
+
|
| 500 |
+
def reduce(self, x, operation, reduced_axes):
|
| 501 |
+
for axis in sorted(reduced_axes, reverse=True):
|
| 502 |
+
if operation == "min":
|
| 503 |
+
x, _ = x.min(dim=axis)
|
| 504 |
+
elif operation == "max":
|
| 505 |
+
x, _ = x.max(dim=axis)
|
| 506 |
+
elif operation in ["sum", "mean", "prod", "any", "all"]:
|
| 507 |
+
x = getattr(x, operation)(dim=axis)
|
| 508 |
+
else:
|
| 509 |
+
raise NotImplementedError("Unknown reduction ", operation)
|
| 510 |
+
return x
|
| 511 |
+
|
| 512 |
+
def transpose(self, x, axes):
|
| 513 |
+
return x.permute(axes)
|
| 514 |
+
|
| 515 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 516 |
+
return self.flow.stack(tensors)
|
| 517 |
+
|
| 518 |
+
def add_axes(self, x, n_axes, pos2len):
|
| 519 |
+
repeats = [-1] * n_axes
|
| 520 |
+
for axis_position, axis_length in pos2len.items():
|
| 521 |
+
x = self.add_axis(x, axis_position)
|
| 522 |
+
repeats[axis_position] = axis_length
|
| 523 |
+
return x.expand(*repeats)
|
| 524 |
+
|
| 525 |
+
def tile(self, x, repeats):
|
| 526 |
+
return x.repeat(repeats)
|
| 527 |
+
|
| 528 |
+
def concat(self, tensors, axis: int):
|
| 529 |
+
return self.flow.concat(tensors, dim=axis)
|
| 530 |
+
|
| 531 |
+
def add_axis(self, x, new_position):
|
| 532 |
+
return self.flow.unsqueeze(x, new_position)
|
| 533 |
+
|
| 534 |
+
def is_float_type(self, x):
|
| 535 |
+
return x.dtype in [self.flow.float16, self.flow.float32, self.flow.float64]
|
| 536 |
+
|
| 537 |
+
def layers(self):
|
| 538 |
+
from .layers import oneflow
|
| 539 |
+
|
| 540 |
+
return oneflow
|
| 541 |
+
|
| 542 |
+
def einsum(self, pattern, *x):
|
| 543 |
+
return self.flow.einsum(pattern, *x)
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
class PaddleBackend(AbstractBackend):
|
| 547 |
+
framework_name = "paddle"
|
| 548 |
+
|
| 549 |
+
def __init__(self):
|
| 550 |
+
import paddle
|
| 551 |
+
|
| 552 |
+
self.paddle = paddle
|
| 553 |
+
|
| 554 |
+
def is_appropriate_type(self, tensor):
|
| 555 |
+
return self.paddle.is_tensor(tensor)
|
| 556 |
+
|
| 557 |
+
def from_numpy(self, x):
|
| 558 |
+
tensor = self.paddle.to_tensor(x)
|
| 559 |
+
tensor.stop_gradient = False
|
| 560 |
+
return tensor
|
| 561 |
+
|
| 562 |
+
def to_numpy(self, x):
|
| 563 |
+
return x.detach().numpy()
|
| 564 |
+
|
| 565 |
+
def arange(self, start, stop):
|
| 566 |
+
return self.paddle.arange(start, stop, dtype=self.paddle.int64)
|
| 567 |
+
|
| 568 |
+
def reduce(self, x, operation, axes):
|
| 569 |
+
if len(axes) == x.ndim:
|
| 570 |
+
# currently paddle returns 1d tensor instead of 0d
|
| 571 |
+
return super().reduce(x, operation, axes).squeeze(0)
|
| 572 |
+
else:
|
| 573 |
+
return super().reduce(x, operation, axes)
|
| 574 |
+
|
| 575 |
+
def transpose(self, x, axes):
|
| 576 |
+
return x.transpose(axes)
|
| 577 |
+
|
| 578 |
+
def add_axes(self, x, n_axes, pos2len):
|
| 579 |
+
repeats = [-1] * n_axes
|
| 580 |
+
for axis_position, axis_length in pos2len.items():
|
| 581 |
+
x = self.add_axis(x, axis_position)
|
| 582 |
+
repeats[axis_position] = axis_length
|
| 583 |
+
return x.expand(repeats)
|
| 584 |
+
|
| 585 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 586 |
+
return self.paddle.stack(tensors)
|
| 587 |
+
|
| 588 |
+
def reshape(self, x, shape):
|
| 589 |
+
return x.reshape(shape)
|
| 590 |
+
|
| 591 |
+
def tile(self, x, repeats):
|
| 592 |
+
return x.tile(repeats)
|
| 593 |
+
|
| 594 |
+
def concat(self, tensors, axis: int):
|
| 595 |
+
return self.paddle.concat(tensors, axis=axis)
|
| 596 |
+
|
| 597 |
+
def add_axis(self, x, new_position):
|
| 598 |
+
return x.unsqueeze(new_position)
|
| 599 |
+
|
| 600 |
+
def is_float_type(self, x):
|
| 601 |
+
return x.dtype in [self.paddle.float16, self.paddle.float32, self.paddle.float64]
|
| 602 |
+
|
| 603 |
+
def layers(self):
|
| 604 |
+
from .layers import paddle
|
| 605 |
+
|
| 606 |
+
return paddle
|
| 607 |
+
|
| 608 |
+
def einsum(self, pattern, *x):
|
| 609 |
+
return self.paddle.einsum(pattern, *x)
|
| 610 |
+
|
| 611 |
+
def shape(self, x):
|
| 612 |
+
return tuple(x.shape)
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
class TinygradBackend(AbstractBackend):
|
| 616 |
+
framework_name = "tinygrad"
|
| 617 |
+
|
| 618 |
+
def __init__(self):
|
| 619 |
+
import tinygrad
|
| 620 |
+
|
| 621 |
+
self.tinygrad = tinygrad
|
| 622 |
+
|
| 623 |
+
def is_appropriate_type(self, tensor):
|
| 624 |
+
return isinstance(tensor, self.tinygrad.Tensor)
|
| 625 |
+
|
| 626 |
+
def from_numpy(self, x):
|
| 627 |
+
return self.tinygrad.Tensor(x)
|
| 628 |
+
|
| 629 |
+
def to_numpy(self, x):
|
| 630 |
+
return x.numpy()
|
| 631 |
+
|
| 632 |
+
def arange(self, start, stop):
|
| 633 |
+
return self.tinygrad.Tensor.arange(start, stop)
|
| 634 |
+
|
| 635 |
+
def shape(self, x):
|
| 636 |
+
return x.shape
|
| 637 |
+
|
| 638 |
+
def reshape(self, x, shape):
|
| 639 |
+
return x.reshape(shape)
|
| 640 |
+
|
| 641 |
+
def transpose(self, x, axes):
|
| 642 |
+
return x.permute(axes)
|
| 643 |
+
|
| 644 |
+
def reduce(self, x, operation, axes):
|
| 645 |
+
for axis in sorted(axes, reverse=True):
|
| 646 |
+
x = getattr(x, operation)(axis=axis)
|
| 647 |
+
return x
|
| 648 |
+
|
| 649 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 650 |
+
return self.tinygrad.Tensor.stack(tensors)
|
| 651 |
+
|
| 652 |
+
def add_axis(self, x, new_position):
|
| 653 |
+
return x.unsqueeze(new_position)
|
| 654 |
+
|
| 655 |
+
def tile(self, x, repeats):
|
| 656 |
+
return x.repeat(repeats)
|
| 657 |
+
|
| 658 |
+
def concat(self, tensors, axis: int):
|
| 659 |
+
return tensors[0].cat(*tensors[1:], dim=axis) if len(tensors) > 1 else tensors[0]
|
| 660 |
+
|
| 661 |
+
def is_float_type(self, x):
|
| 662 |
+
return self.tinygrad.dtypes.is_float(x.dtype)
|
| 663 |
+
|
| 664 |
+
def einsum(self, pattern, *x):
|
| 665 |
+
return self.tinygrad.Tensor.einsum(pattern, *x)
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
class PyTensorBackend(AbstractBackend):
|
| 669 |
+
framework_name = "pytensor"
|
| 670 |
+
|
| 671 |
+
def __init__(self):
|
| 672 |
+
from pytensor import tensor
|
| 673 |
+
|
| 674 |
+
self.pt = tensor
|
| 675 |
+
|
| 676 |
+
def is_appropriate_type(self, tensor):
|
| 677 |
+
return isinstance(tensor, self.pt.TensorVariable)
|
| 678 |
+
|
| 679 |
+
def is_float_type(self, x):
|
| 680 |
+
return x.dtype in self.pt.type.float_dtypes
|
| 681 |
+
|
| 682 |
+
def from_numpy(self, x):
|
| 683 |
+
return self.pt.as_tensor(x)
|
| 684 |
+
|
| 685 |
+
def to_numpy(self, x):
|
| 686 |
+
return x.eval() # Will only work if there are no symbolic inputs
|
| 687 |
+
|
| 688 |
+
def create_symbol(self, shape):
|
| 689 |
+
if not isinstance(shape, tuple | list):
|
| 690 |
+
shape = (shape,)
|
| 691 |
+
return self.pt.tensor(shape=shape)
|
| 692 |
+
|
| 693 |
+
def eval_symbol(self, symbol, symbol_value_pairs):
|
| 694 |
+
return symbol.eval(dict(symbol_value_pairs))
|
| 695 |
+
|
| 696 |
+
def arange(self, start, stop):
|
| 697 |
+
return self.pt.arange(start, stop)
|
| 698 |
+
|
| 699 |
+
def shape(self, x):
|
| 700 |
+
# use the static shape dimensions where known
|
| 701 |
+
return tuple(
|
| 702 |
+
static_dim if static_dim is not None else symbolic_dim
|
| 703 |
+
for static_dim, symbolic_dim in zip(x.type.shape, x.shape)
|
| 704 |
+
)
|
| 705 |
+
|
| 706 |
+
def stack_on_zeroth_dimension(self, tensors: list):
|
| 707 |
+
return self.pt.stack(tensors)
|
| 708 |
+
|
| 709 |
+
def tile(self, x, repeats):
|
| 710 |
+
return self.pt.tile(x, repeats)
|
| 711 |
+
|
| 712 |
+
def concat(self, tensors, axis: int):
|
| 713 |
+
return self.pt.concatenate(tensors, axis=axis)
|
| 714 |
+
|
| 715 |
+
def add_axis(self, x, new_position):
|
| 716 |
+
return self.pt.expand_dims(x, new_position)
|
| 717 |
+
|
| 718 |
+
def einsum(self, pattern, *x):
|
| 719 |
+
return self.pt.einsum(pattern, *x)
|
build/torch-cuda/_ops.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
def get_backend() -> str:
|
| 4 |
+
"""Detect the backend by inspecting torch."""
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
if hasattr(torch, "neuron"):
|
| 8 |
+
# Needs to be sorted before specific Torch builds, since Neuron
|
| 9 |
+
# extension can be loaded into e.g. CUDA Torch builds.
|
| 10 |
+
return "neuron"
|
| 11 |
+
elif torch.version.cuda is not None:
|
| 12 |
+
return "cuda"
|
| 13 |
+
elif torch.version.hip is not None:
|
| 14 |
+
return "rocm"
|
| 15 |
+
elif torch.backends.mps.is_available():
|
| 16 |
+
return "metal"
|
| 17 |
+
elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
|
| 18 |
+
return "xpu"
|
| 19 |
+
else:
|
| 20 |
+
return "cpu"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _find_ops_name() -> str:
|
| 24 |
+
kernel_name = "einops"
|
| 25 |
+
unique_id = "d45adda"
|
| 26 |
+
backend = get_backend()
|
| 27 |
+
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
_OPS_NAME = _find_ops_name()
|
| 31 |
+
|
| 32 |
+
ops = getattr(torch.ops, _OPS_NAME)
|
| 33 |
+
|
| 34 |
+
def add_op_namespace_prefix(op_name: str) -> str:
|
| 35 |
+
"""
|
| 36 |
+
Prefix op by namespace.
|
| 37 |
+
"""
|
| 38 |
+
return f"{_OPS_NAME}::{op_name}"
|
build/torch-cuda/_torch_specific.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Specialization of einops for torch.
|
| 3 |
+
|
| 4 |
+
Unfortunately, torch's jit scripting mechanism isn't strong enough,
|
| 5 |
+
and to have scripting supported at least for layers,
|
| 6 |
+
a number of additional moves is needed.
|
| 7 |
+
|
| 8 |
+
Design of main operations (dynamic resolution by lookup) is unlikely
|
| 9 |
+
to be implemented by torch.jit.script,
|
| 10 |
+
but torch.compile seems to work with operations just fine.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import warnings
|
| 14 |
+
from typing import Dict, List, Tuple
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
from .einops import TransformRecipe, _reconstruct_from_shape_uncached
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class TorchJitBackend:
|
| 21 |
+
"""
|
| 22 |
+
Completely static backend that mimics part of normal backend functionality
|
| 23 |
+
but restricted to be within torchscript.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
@staticmethod
|
| 27 |
+
def reduce(x: torch.Tensor, operation: str, reduced_axes: List[int]):
|
| 28 |
+
if operation == "min":
|
| 29 |
+
return x.amin(dim=reduced_axes)
|
| 30 |
+
elif operation == "max":
|
| 31 |
+
return x.amax(dim=reduced_axes)
|
| 32 |
+
elif operation == "sum":
|
| 33 |
+
return x.sum(dim=reduced_axes)
|
| 34 |
+
elif operation == "mean":
|
| 35 |
+
return x.mean(dim=reduced_axes)
|
| 36 |
+
elif operation == "prod":
|
| 37 |
+
for i in list(sorted(reduced_axes))[::-1]:
|
| 38 |
+
x = x.prod(dim=i)
|
| 39 |
+
return x
|
| 40 |
+
else:
|
| 41 |
+
raise NotImplementedError("Unknown reduction ", operation)
|
| 42 |
+
|
| 43 |
+
@staticmethod
|
| 44 |
+
def transpose(x, axes: List[int]):
|
| 45 |
+
return x.permute(axes)
|
| 46 |
+
|
| 47 |
+
@staticmethod
|
| 48 |
+
def stack_on_zeroth_dimension(tensors: List[torch.Tensor]):
|
| 49 |
+
return torch.stack(tensors)
|
| 50 |
+
|
| 51 |
+
@staticmethod
|
| 52 |
+
def tile(x, repeats: List[int]):
|
| 53 |
+
return x.repeat(repeats)
|
| 54 |
+
|
| 55 |
+
@staticmethod
|
| 56 |
+
def add_axes(x, n_axes: int, pos2len: Dict[int, int]):
|
| 57 |
+
repeats = [-1] * n_axes
|
| 58 |
+
for axis_position, axis_length in pos2len.items():
|
| 59 |
+
x = torch.unsqueeze(x, axis_position)
|
| 60 |
+
repeats[axis_position] = axis_length
|
| 61 |
+
return x.expand(repeats)
|
| 62 |
+
|
| 63 |
+
@staticmethod
|
| 64 |
+
def is_float_type(x):
|
| 65 |
+
return x.dtype in [torch.float16, torch.float32, torch.float64, torch.bfloat16]
|
| 66 |
+
|
| 67 |
+
@staticmethod
|
| 68 |
+
def shape(x):
|
| 69 |
+
return x.shape
|
| 70 |
+
|
| 71 |
+
@staticmethod
|
| 72 |
+
def reshape(x, shape: List[int]):
|
| 73 |
+
return x.reshape(shape)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# mirrors einops.einops._apply_recipe
|
| 77 |
+
def apply_for_scriptable_torch(
|
| 78 |
+
recipe: TransformRecipe, tensor: torch.Tensor, reduction_type: str, axes_dims: List[Tuple[str, int]]
|
| 79 |
+
) -> torch.Tensor:
|
| 80 |
+
backend = TorchJitBackend
|
| 81 |
+
(
|
| 82 |
+
init_shapes,
|
| 83 |
+
axes_reordering,
|
| 84 |
+
reduced_axes,
|
| 85 |
+
added_axes,
|
| 86 |
+
final_shapes,
|
| 87 |
+
n_axes_w_added,
|
| 88 |
+
) = _reconstruct_from_shape_uncached(recipe, backend.shape(tensor), axes_dims=axes_dims)
|
| 89 |
+
if init_shapes is not None:
|
| 90 |
+
tensor = backend.reshape(tensor, init_shapes)
|
| 91 |
+
if axes_reordering is not None:
|
| 92 |
+
tensor = backend.transpose(tensor, axes_reordering)
|
| 93 |
+
if len(reduced_axes) > 0:
|
| 94 |
+
tensor = backend.reduce(tensor, operation=reduction_type, reduced_axes=reduced_axes)
|
| 95 |
+
if len(added_axes) > 0:
|
| 96 |
+
tensor = backend.add_axes(tensor, n_axes=n_axes_w_added, pos2len=added_axes)
|
| 97 |
+
if final_shapes is not None:
|
| 98 |
+
tensor = backend.reshape(tensor, final_shapes)
|
| 99 |
+
return tensor
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def allow_ops_in_compiled_graph():
|
| 103 |
+
if hasattr(torch, "__version__") and torch.__version__[0] < "2":
|
| 104 |
+
# torch._dynamo and torch.compile appear in pytorch 2.0
|
| 105 |
+
return
|
| 106 |
+
try:
|
| 107 |
+
from torch._dynamo import allow_in_graph
|
| 108 |
+
except ImportError:
|
| 109 |
+
warnings.warn("allow_ops_in_compiled_graph failed to import torch: ensure pytorch >=2.0", ImportWarning)
|
| 110 |
+
return
|
| 111 |
+
|
| 112 |
+
from .einops import rearrange, reduce, repeat, einsum
|
| 113 |
+
from .packing import pack, unpack
|
| 114 |
+
|
| 115 |
+
allow_in_graph(rearrange)
|
| 116 |
+
allow_in_graph(reduce)
|
| 117 |
+
allow_in_graph(repeat)
|
| 118 |
+
allow_in_graph(einsum)
|
| 119 |
+
allow_in_graph(pack)
|
| 120 |
+
allow_in_graph(unpack)
|
| 121 |
+
|
| 122 |
+
# CF: https://github.com/pytorch/pytorch/blob/2df939aacac68e9621fbd5d876c78d86e72b41e2/torch/_dynamo/__init__.py#L222
|
| 123 |
+
global _ops_were_registered_in_torchdynamo
|
| 124 |
+
_ops_were_registered_in_torchdynamo = True
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# module import automatically registers ops in torchdynamo
|
| 128 |
+
allow_ops_in_compiled_graph()
|
build/torch-cuda/array_api.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Tuple, Sequence
|
| 2 |
+
from .einops import Tensor, Reduction, EinopsError, _prepare_transformation_recipe, _apply_recipe_array_api
|
| 3 |
+
from .packing import analyze_pattern, prod
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def reduce(tensor: Tensor, pattern: str, reduction: Reduction, **axes_lengths: int) -> Tensor:
|
| 7 |
+
if isinstance(tensor, list):
|
| 8 |
+
if len(tensor) == 0:
|
| 9 |
+
raise TypeError("Einops can't be applied to an empty list")
|
| 10 |
+
xp = tensor[0].__array_namespace__()
|
| 11 |
+
tensor = xp.stack(tensor)
|
| 12 |
+
else:
|
| 13 |
+
xp = tensor.__array_namespace__()
|
| 14 |
+
try:
|
| 15 |
+
hashable_axes_lengths = tuple(axes_lengths.items())
|
| 16 |
+
recipe = _prepare_transformation_recipe(pattern, reduction, axes_names=tuple(axes_lengths), ndim=tensor.ndim)
|
| 17 |
+
return _apply_recipe_array_api(
|
| 18 |
+
xp,
|
| 19 |
+
recipe=recipe,
|
| 20 |
+
tensor=tensor,
|
| 21 |
+
reduction_type=reduction,
|
| 22 |
+
axes_lengths=hashable_axes_lengths,
|
| 23 |
+
)
|
| 24 |
+
except EinopsError as e:
|
| 25 |
+
message = ' Error while processing {}-reduction pattern "{}".'.format(reduction, pattern)
|
| 26 |
+
if not isinstance(tensor, list):
|
| 27 |
+
message += "\n Input tensor shape: {}. ".format(tensor.shape)
|
| 28 |
+
else:
|
| 29 |
+
message += "\n Input is list. "
|
| 30 |
+
message += "Additional info: {}.".format(axes_lengths)
|
| 31 |
+
raise EinopsError(message + "\n {}".format(e))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def repeat(tensor: Tensor, pattern: str, **axes_lengths) -> Tensor:
|
| 35 |
+
return reduce(tensor, pattern, reduction="repeat", **axes_lengths)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def rearrange(tensor: Tensor, pattern: str, **axes_lengths) -> Tensor:
|
| 39 |
+
return reduce(tensor, pattern, reduction="rearrange", **axes_lengths)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def asnumpy(tensor: Tensor):
|
| 43 |
+
import numpy as np
|
| 44 |
+
|
| 45 |
+
return np.from_dlpack(tensor)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
Shape = Tuple
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def pack(tensors: Sequence[Tensor], pattern: str) -> Tuple[Tensor, List[Shape]]:
|
| 52 |
+
n_axes_before, n_axes_after, min_axes = analyze_pattern(pattern, "pack")
|
| 53 |
+
xp = tensors[0].__array_namespace__()
|
| 54 |
+
|
| 55 |
+
reshaped_tensors: List[Tensor] = []
|
| 56 |
+
packed_shapes: List[Shape] = []
|
| 57 |
+
for i, tensor in enumerate(tensors):
|
| 58 |
+
shape = tensor.shape
|
| 59 |
+
if len(shape) < min_axes:
|
| 60 |
+
raise EinopsError(
|
| 61 |
+
f"packed tensor #{i} (enumeration starts with 0) has shape {shape}, "
|
| 62 |
+
f"while pattern {pattern} assumes at least {min_axes} axes"
|
| 63 |
+
)
|
| 64 |
+
axis_after_packed_axes = len(shape) - n_axes_after
|
| 65 |
+
packed_shapes.append(shape[n_axes_before:axis_after_packed_axes])
|
| 66 |
+
reshaped_tensors.append(xp.reshape(tensor, (*shape[:n_axes_before], -1, *shape[axis_after_packed_axes:])))
|
| 67 |
+
|
| 68 |
+
return xp.concat(reshaped_tensors, axis=n_axes_before), packed_shapes
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def unpack(tensor: Tensor, packed_shapes: List[Shape], pattern: str) -> List[Tensor]:
|
| 72 |
+
xp = tensor.__array_namespace__()
|
| 73 |
+
n_axes_before, n_axes_after, min_axes = analyze_pattern(pattern, opname="unpack")
|
| 74 |
+
|
| 75 |
+
# backend = get_backend(tensor)
|
| 76 |
+
input_shape = tensor.shape
|
| 77 |
+
if len(input_shape) != n_axes_before + 1 + n_axes_after:
|
| 78 |
+
raise EinopsError(f"unpack(..., {pattern}) received input of wrong dim with shape {input_shape}")
|
| 79 |
+
|
| 80 |
+
unpacked_axis: int = n_axes_before
|
| 81 |
+
|
| 82 |
+
lengths_of_composed_axes: List[int] = [-1 if -1 in p_shape else prod(p_shape) for p_shape in packed_shapes]
|
| 83 |
+
|
| 84 |
+
n_unknown_composed_axes = sum(x == -1 for x in lengths_of_composed_axes)
|
| 85 |
+
if n_unknown_composed_axes > 1:
|
| 86 |
+
raise EinopsError(
|
| 87 |
+
f"unpack(..., {pattern}) received more than one -1 in {packed_shapes} and can't infer dimensions"
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
# following manipulations allow to skip some shape verifications
|
| 91 |
+
# and leave it to backends
|
| 92 |
+
|
| 93 |
+
# [[], [2, 3], [4], [-1, 5], [6]] < examples of packed_axis
|
| 94 |
+
# split positions when computed should be
|
| 95 |
+
# [0, 1, 7, 11, N-6 , N ], where N = length of axis
|
| 96 |
+
split_positions = [0] * len(packed_shapes) + [input_shape[unpacked_axis]]
|
| 97 |
+
if n_unknown_composed_axes == 0:
|
| 98 |
+
for i, x in enumerate(lengths_of_composed_axes[:-1]):
|
| 99 |
+
split_positions[i + 1] = split_positions[i] + x
|
| 100 |
+
else:
|
| 101 |
+
unknown_composed_axis: int = lengths_of_composed_axes.index(-1)
|
| 102 |
+
for i in range(unknown_composed_axis):
|
| 103 |
+
split_positions[i + 1] = split_positions[i] + lengths_of_composed_axes[i]
|
| 104 |
+
for j in range(unknown_composed_axis + 1, len(lengths_of_composed_axes))[::-1]:
|
| 105 |
+
split_positions[j] = split_positions[j + 1] - lengths_of_composed_axes[j]
|
| 106 |
+
|
| 107 |
+
shape_start = input_shape[:unpacked_axis]
|
| 108 |
+
shape_end = input_shape[unpacked_axis + 1 :]
|
| 109 |
+
slice_filler = (slice(None, None),) * unpacked_axis
|
| 110 |
+
try:
|
| 111 |
+
return [
|
| 112 |
+
xp.reshape(
|
| 113 |
+
# shortest way slice arbitrary axis
|
| 114 |
+
tensor[(*slice_filler, slice(split_positions[i], split_positions[i + 1]), ...)],
|
| 115 |
+
(*shape_start, *element_shape, *shape_end),
|
| 116 |
+
)
|
| 117 |
+
for i, element_shape in enumerate(packed_shapes)
|
| 118 |
+
]
|
| 119 |
+
except Exception:
|
| 120 |
+
# this hits if there is an error during reshapes, which means passed shapes were incorrect
|
| 121 |
+
raise RuntimeError(
|
| 122 |
+
f'Error during unpack(..., "{pattern}"): could not split axis of size {split_positions[-1]}'
|
| 123 |
+
f" into requested {packed_shapes}"
|
| 124 |
+
)
|
build/torch-cuda/einops.py
ADDED
|
@@ -0,0 +1,916 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import functools
|
| 2 |
+
import itertools
|
| 3 |
+
import string
|
| 4 |
+
import typing
|
| 5 |
+
from collections import OrderedDict
|
| 6 |
+
from typing import Set, Tuple, List, Dict, Union, Callable, Optional, TypeVar, cast, Any
|
| 7 |
+
|
| 8 |
+
if typing.TYPE_CHECKING:
|
| 9 |
+
# for docstrings in pycharm
|
| 10 |
+
import numpy as np # noqa E401
|
| 11 |
+
|
| 12 |
+
from . import EinopsError
|
| 13 |
+
from ._backends import get_backend
|
| 14 |
+
from .parsing import ParsedExpression, _ellipsis, AnonymousAxis
|
| 15 |
+
|
| 16 |
+
Tensor = TypeVar("Tensor")
|
| 17 |
+
ReductionCallable = Callable[[Tensor, Tuple[int, ...]], Tensor]
|
| 18 |
+
Reduction = Union[str, ReductionCallable]
|
| 19 |
+
Size = typing.Any
|
| 20 |
+
|
| 21 |
+
_reductions = ("min", "max", "sum", "mean", "prod", "any", "all")
|
| 22 |
+
|
| 23 |
+
# magic integers are required to stay within
|
| 24 |
+
# traceable subset of language
|
| 25 |
+
_unknown_axis_length = -999999
|
| 26 |
+
_expected_axis_length = -99999
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _product(sequence: List[int]) -> int:
|
| 30 |
+
"""minimalistic product that works both with numbers and symbols. Supports empty lists"""
|
| 31 |
+
result = 1
|
| 32 |
+
for element in sequence:
|
| 33 |
+
result *= element
|
| 34 |
+
return result
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _reduce_axes(tensor, reduction_type: Reduction, reduced_axes: List[int], backend):
|
| 38 |
+
if callable(reduction_type):
|
| 39 |
+
# custom callable
|
| 40 |
+
return reduction_type(tensor, tuple(reduced_axes))
|
| 41 |
+
else:
|
| 42 |
+
# one of built-in operations
|
| 43 |
+
assert reduction_type in _reductions
|
| 44 |
+
if reduction_type == "mean":
|
| 45 |
+
if not backend.is_float_type(tensor):
|
| 46 |
+
raise NotImplementedError("reduce_mean is not available for non-floating tensors")
|
| 47 |
+
return backend.reduce(tensor, reduction_type, tuple(reduced_axes))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _optimize_transformation(init_shapes, reduced_axes, axes_reordering, final_shapes):
|
| 51 |
+
# 'collapses' neighboring axes if those participate in the result pattern in the same order
|
| 52 |
+
# TODO add support for added_axes
|
| 53 |
+
assert len(axes_reordering) + len(reduced_axes) == len(init_shapes)
|
| 54 |
+
# joining consecutive axes that will be reduced
|
| 55 |
+
# possibly we can skip this if all backends can optimize this (not sure)
|
| 56 |
+
reduced_axes = tuple(sorted(reduced_axes))
|
| 57 |
+
for i in range(len(reduced_axes) - 1)[::-1]:
|
| 58 |
+
if reduced_axes[i] + 1 == reduced_axes[i + 1]:
|
| 59 |
+
removed_axis = reduced_axes[i + 1]
|
| 60 |
+
removed_length = init_shapes[removed_axis]
|
| 61 |
+
init_shapes = init_shapes[:removed_axis] + init_shapes[removed_axis + 1 :]
|
| 62 |
+
init_shapes[removed_axis - 1] *= removed_length
|
| 63 |
+
reduced_axes = reduced_axes[: i + 1] + tuple(axis - 1 for axis in reduced_axes[i + 2 :])
|
| 64 |
+
|
| 65 |
+
# removing axes that are moved together during reshape
|
| 66 |
+
def build_mapping():
|
| 67 |
+
init_to_final = {}
|
| 68 |
+
for axis in range(len(init_shapes)):
|
| 69 |
+
if axis in reduced_axes:
|
| 70 |
+
init_to_final[axis] = None
|
| 71 |
+
else:
|
| 72 |
+
after_reduction = sum(x is not None for x in init_to_final.values())
|
| 73 |
+
init_to_final[axis] = list(axes_reordering).index(after_reduction)
|
| 74 |
+
return init_to_final
|
| 75 |
+
|
| 76 |
+
init_axis_to_final_axis = build_mapping()
|
| 77 |
+
|
| 78 |
+
for init_axis in range(len(init_shapes) - 1)[::-1]:
|
| 79 |
+
if init_axis_to_final_axis[init_axis] is None:
|
| 80 |
+
continue
|
| 81 |
+
if init_axis_to_final_axis[init_axis + 1] is None:
|
| 82 |
+
continue
|
| 83 |
+
if init_axis_to_final_axis[init_axis] + 1 == init_axis_to_final_axis[init_axis + 1]:
|
| 84 |
+
removed_axis = init_axis + 1
|
| 85 |
+
removed_length = init_shapes[removed_axis]
|
| 86 |
+
removed_axis_after_reduction = sum(x not in reduced_axes for x in range(removed_axis))
|
| 87 |
+
|
| 88 |
+
reduced_axes = tuple(axis if axis < removed_axis else axis - 1 for axis in reduced_axes)
|
| 89 |
+
init_shapes = init_shapes[:removed_axis] + init_shapes[removed_axis + 1 :]
|
| 90 |
+
init_shapes[removed_axis - 1] *= removed_length
|
| 91 |
+
old_reordering = axes_reordering
|
| 92 |
+
axes_reordering = []
|
| 93 |
+
for axis in old_reordering:
|
| 94 |
+
if axis == removed_axis_after_reduction:
|
| 95 |
+
pass
|
| 96 |
+
elif axis < removed_axis_after_reduction:
|
| 97 |
+
axes_reordering.append(axis)
|
| 98 |
+
else:
|
| 99 |
+
axes_reordering.append(axis - 1)
|
| 100 |
+
init_axis_to_final_axis = build_mapping()
|
| 101 |
+
|
| 102 |
+
return init_shapes, reduced_axes, axes_reordering, final_shapes
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
CookedRecipe = Tuple[Optional[List[int]], Optional[List[int]], List[int], Dict[int, int], Optional[List[int]], int]
|
| 106 |
+
|
| 107 |
+
# Actual type is tuple[tuple[str, int], ...]
|
| 108 |
+
# However torch.jit.script does not "understand" the correct type,
|
| 109 |
+
# and torch_specific will use list version.
|
| 110 |
+
HashableAxesLengths = Tuple[Tuple[str, int], ...]
|
| 111 |
+
FakeHashableAxesLengths = List[Tuple[str, int]]
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class TransformRecipe:
|
| 115 |
+
"""
|
| 116 |
+
Recipe describes actual computation pathway.
|
| 117 |
+
Recipe can be applied to a tensor or variable.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
# structure is non-mutable. In future, this can be non-mutable dataclass (python 3.7+)
|
| 121 |
+
# update: pytorch 2.0 torch.jit.script seems to have problems with dataclasses unless they were explicitly provided
|
| 122 |
+
|
| 123 |
+
def __init__(
|
| 124 |
+
self,
|
| 125 |
+
# list of sizes (or just sizes) for elementary axes as they appear in left expression.
|
| 126 |
+
# this is what (after computing unknown parts) will be a shape after first transposition.
|
| 127 |
+
# This does not include any ellipsis dimensions.
|
| 128 |
+
elementary_axes_lengths: List[int],
|
| 129 |
+
# if additional axes are provided, they should be set in prev array
|
| 130 |
+
# This shows mapping from name to position
|
| 131 |
+
axis_name2elementary_axis: Dict[str, int],
|
| 132 |
+
# each dimension in input can help to reconstruct length of one elementary axis
|
| 133 |
+
# or verify one of dimensions. Each element points to element of elementary_axes_lengths.
|
| 134 |
+
input_composition_known_unknown: List[Tuple[List[int], List[int]]],
|
| 135 |
+
# permutation applied to elementary axes, if ellipsis is absent
|
| 136 |
+
axes_permutation: List[int],
|
| 137 |
+
# permutation puts reduced axes in the end, we only need to know the first position.
|
| 138 |
+
first_reduced_axis: int,
|
| 139 |
+
# at which positions which of elementary axes should appear. Axis position -> axis index.
|
| 140 |
+
added_axes: Dict[int, int],
|
| 141 |
+
# ids of axes as they appear in result, again pointers to elementary_axes_lengths,
|
| 142 |
+
# only used to infer result dimensions
|
| 143 |
+
output_composite_axes: List[List[int]],
|
| 144 |
+
):
|
| 145 |
+
self.elementary_axes_lengths: List[int] = elementary_axes_lengths
|
| 146 |
+
self.axis_name2elementary_axis: Dict[str, int] = axis_name2elementary_axis
|
| 147 |
+
self.input_composition_known_unknown: List[Tuple[List[int], List[int]]] = input_composition_known_unknown
|
| 148 |
+
self.axes_permutation: List[int] = axes_permutation
|
| 149 |
+
|
| 150 |
+
self.first_reduced_axis: int = first_reduced_axis
|
| 151 |
+
self.added_axes: Dict[int, int] = added_axes
|
| 152 |
+
self.output_composite_axes: List[List[int]] = output_composite_axes
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _reconstruct_from_shape_uncached(
|
| 156 |
+
self: TransformRecipe, shape: List[int], axes_dims: FakeHashableAxesLengths
|
| 157 |
+
) -> CookedRecipe:
|
| 158 |
+
"""
|
| 159 |
+
Reconstruct all actual parameters using shape.
|
| 160 |
+
Shape is a tuple that may contain integers, shape symbols (tf, theano) and UnknownSize (tf, previously mxnet)
|
| 161 |
+
known axes can be integers or symbols, but not Nones.
|
| 162 |
+
"""
|
| 163 |
+
# magic number
|
| 164 |
+
need_init_reshape = False
|
| 165 |
+
|
| 166 |
+
# last axis is allocated for collapsed ellipsis
|
| 167 |
+
axes_lengths: List[int] = list(self.elementary_axes_lengths)
|
| 168 |
+
for axis, dim in axes_dims:
|
| 169 |
+
axes_lengths[self.axis_name2elementary_axis[axis]] = dim
|
| 170 |
+
|
| 171 |
+
for input_axis, (known_axes, unknown_axes) in enumerate(self.input_composition_known_unknown):
|
| 172 |
+
length = shape[input_axis]
|
| 173 |
+
if len(known_axes) == 0 and len(unknown_axes) == 1:
|
| 174 |
+
# shortcut for the most common case
|
| 175 |
+
axes_lengths[unknown_axes[0]] = length
|
| 176 |
+
continue
|
| 177 |
+
|
| 178 |
+
known_product = 1
|
| 179 |
+
for axis in known_axes:
|
| 180 |
+
known_product *= axes_lengths[axis]
|
| 181 |
+
|
| 182 |
+
if len(unknown_axes) == 0:
|
| 183 |
+
if isinstance(length, int) and isinstance(known_product, int) and length != known_product:
|
| 184 |
+
raise EinopsError(f"Shape mismatch, {length} != {known_product}")
|
| 185 |
+
else:
|
| 186 |
+
# assert len(unknown_axes) == 1, 'this is enforced when recipe is created, so commented out'
|
| 187 |
+
if isinstance(length, int) and isinstance(known_product, int) and length % known_product != 0:
|
| 188 |
+
raise EinopsError(f"Shape mismatch, can't divide axis of length {length} in chunks of {known_product}")
|
| 189 |
+
|
| 190 |
+
unknown_axis = unknown_axes[0]
|
| 191 |
+
inferred_length: int = length // known_product
|
| 192 |
+
axes_lengths[unknown_axis] = inferred_length
|
| 193 |
+
|
| 194 |
+
if len(known_axes) + len(unknown_axes) != 1:
|
| 195 |
+
need_init_reshape = True
|
| 196 |
+
|
| 197 |
+
# at this point all axes_lengths are computed (either have values or variables, but not Nones)
|
| 198 |
+
|
| 199 |
+
# elementary axes are ordered as they appear in input, then all added axes
|
| 200 |
+
init_shapes: Optional[List[int]] = axes_lengths[: len(self.axes_permutation)] if need_init_reshape else None
|
| 201 |
+
|
| 202 |
+
need_final_reshape = False
|
| 203 |
+
final_shapes: List[int] = []
|
| 204 |
+
for grouping in self.output_composite_axes:
|
| 205 |
+
lengths = [axes_lengths[elementary_axis] for elementary_axis in grouping]
|
| 206 |
+
final_shapes.append(_product(lengths))
|
| 207 |
+
if len(lengths) != 1:
|
| 208 |
+
need_final_reshape = True
|
| 209 |
+
|
| 210 |
+
added_axes: Dict[int, int] = {
|
| 211 |
+
pos: axes_lengths[pos_in_elementary] for pos, pos_in_elementary in self.added_axes.items()
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
# this list can be empty
|
| 215 |
+
reduced_axes = list(range(self.first_reduced_axis, len(self.axes_permutation)))
|
| 216 |
+
|
| 217 |
+
n_axes_after_adding_axes = len(added_axes) + len(self.axes_permutation)
|
| 218 |
+
|
| 219 |
+
axes_reordering: Optional[List[int]] = self.axes_permutation
|
| 220 |
+
if self.axes_permutation == list(range(len(self.axes_permutation))):
|
| 221 |
+
axes_reordering = None
|
| 222 |
+
|
| 223 |
+
_final_shapes = final_shapes if need_final_reshape else None
|
| 224 |
+
return init_shapes, axes_reordering, reduced_axes, added_axes, _final_shapes, n_axes_after_adding_axes
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
_reconstruct_from_shape = functools.lru_cache(1024)(_reconstruct_from_shape_uncached)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def _apply_recipe(
|
| 231 |
+
backend, recipe: TransformRecipe, tensor: Tensor, reduction_type: Reduction, axes_lengths: HashableAxesLengths
|
| 232 |
+
) -> Tensor:
|
| 233 |
+
# this method implements actual work for all backends for 3 operations
|
| 234 |
+
try:
|
| 235 |
+
init_shapes, axes_reordering, reduced_axes, added_axes, final_shapes, n_axes_w_added = _reconstruct_from_shape(
|
| 236 |
+
recipe, backend.shape(tensor), axes_lengths
|
| 237 |
+
)
|
| 238 |
+
except TypeError:
|
| 239 |
+
# shape or one of passed axes lengths is not hashable (i.e. they are symbols)
|
| 240 |
+
_result = _reconstruct_from_shape_uncached(recipe, backend.shape(tensor), axes_lengths)
|
| 241 |
+
(init_shapes, axes_reordering, reduced_axes, added_axes, final_shapes, n_axes_w_added) = _result
|
| 242 |
+
if init_shapes is not None:
|
| 243 |
+
tensor = backend.reshape(tensor, init_shapes)
|
| 244 |
+
if axes_reordering is not None:
|
| 245 |
+
tensor = backend.transpose(tensor, axes_reordering)
|
| 246 |
+
if len(reduced_axes) > 0:
|
| 247 |
+
tensor = _reduce_axes(tensor, reduction_type=reduction_type, reduced_axes=reduced_axes, backend=backend)
|
| 248 |
+
if len(added_axes) > 0:
|
| 249 |
+
tensor = backend.add_axes(tensor, n_axes=n_axes_w_added, pos2len=added_axes)
|
| 250 |
+
if final_shapes is not None:
|
| 251 |
+
tensor = backend.reshape(tensor, final_shapes)
|
| 252 |
+
return tensor
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _apply_recipe_array_api(
|
| 256 |
+
xp, recipe: TransformRecipe, tensor: Tensor, reduction_type: Reduction, axes_lengths: HashableAxesLengths
|
| 257 |
+
) -> Tensor:
|
| 258 |
+
# completely-inline implementation
|
| 259 |
+
init_shapes, axes_reordering, reduced_axes, added_axes, final_shapes, n_axes_w_added = _reconstruct_from_shape(
|
| 260 |
+
recipe, tensor.shape, axes_lengths
|
| 261 |
+
)
|
| 262 |
+
if init_shapes is not None:
|
| 263 |
+
tensor = xp.reshape(tensor, init_shapes)
|
| 264 |
+
if axes_reordering is not None:
|
| 265 |
+
tensor = xp.permute_dims(tensor, axes_reordering)
|
| 266 |
+
if len(reduced_axes) > 0:
|
| 267 |
+
if callable(reduction_type):
|
| 268 |
+
# custom callable
|
| 269 |
+
tensor = reduction_type(tensor, tuple(reduced_axes))
|
| 270 |
+
else:
|
| 271 |
+
# one of built-in operations
|
| 272 |
+
assert reduction_type in _reductions
|
| 273 |
+
tensor = getattr(xp, reduction_type)(tensor, axis=tuple(reduced_axes))
|
| 274 |
+
if len(added_axes) > 0:
|
| 275 |
+
# we use broadcasting
|
| 276 |
+
for axis_position, axis_length in added_axes.items():
|
| 277 |
+
tensor = xp.expand_dims(tensor, axis=axis_position)
|
| 278 |
+
|
| 279 |
+
final_shape = list(tensor.shape)
|
| 280 |
+
for axis_position, axis_length in added_axes.items():
|
| 281 |
+
final_shape[axis_position] = axis_length
|
| 282 |
+
|
| 283 |
+
tensor = xp.broadcast_to(tensor, final_shape)
|
| 284 |
+
if final_shapes is not None:
|
| 285 |
+
tensor = xp.reshape(tensor, final_shapes)
|
| 286 |
+
return tensor
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
@functools.lru_cache(256)
|
| 290 |
+
def _prepare_transformation_recipe(
|
| 291 |
+
pattern: str,
|
| 292 |
+
operation: Reduction,
|
| 293 |
+
axes_names: Tuple[str, ...],
|
| 294 |
+
ndim: int,
|
| 295 |
+
) -> TransformRecipe:
|
| 296 |
+
"""Perform initial parsing of pattern and provided supplementary info
|
| 297 |
+
axes_lengths is a tuple of tuples (axis_name, axis_length)
|
| 298 |
+
"""
|
| 299 |
+
left_str, rght_str = pattern.split("->")
|
| 300 |
+
left = ParsedExpression(left_str)
|
| 301 |
+
rght = ParsedExpression(rght_str)
|
| 302 |
+
|
| 303 |
+
# checking that axes are in agreement - new axes appear only in repeat, while disappear only in reduction
|
| 304 |
+
if not left.has_ellipsis and rght.has_ellipsis:
|
| 305 |
+
raise EinopsError("Ellipsis found in right side, but not left side of a pattern {}".format(pattern))
|
| 306 |
+
if left.has_ellipsis and left.has_ellipsis_parenthesized:
|
| 307 |
+
raise EinopsError("Ellipsis inside parenthesis in the left side is not allowed: {}".format(pattern))
|
| 308 |
+
if operation == "rearrange":
|
| 309 |
+
if left.has_non_unitary_anonymous_axes or rght.has_non_unitary_anonymous_axes:
|
| 310 |
+
raise EinopsError("Non-unitary anonymous axes are not supported in rearrange (exception is length 1)")
|
| 311 |
+
difference = set.symmetric_difference(left.identifiers, rght.identifiers)
|
| 312 |
+
if len(difference) > 0:
|
| 313 |
+
raise EinopsError("Identifiers only on one side of expression (should be on both): {}".format(difference))
|
| 314 |
+
elif operation == "repeat":
|
| 315 |
+
difference = set.difference(left.identifiers, rght.identifiers)
|
| 316 |
+
if len(difference) > 0:
|
| 317 |
+
raise EinopsError("Unexpected identifiers on the left side of repeat: {}".format(difference))
|
| 318 |
+
axes_without_size = set.difference(
|
| 319 |
+
{ax for ax in rght.identifiers if not isinstance(ax, AnonymousAxis)},
|
| 320 |
+
{*left.identifiers, *axes_names},
|
| 321 |
+
)
|
| 322 |
+
if len(axes_without_size) > 0:
|
| 323 |
+
raise EinopsError("Specify sizes for new axes in repeat: {}".format(axes_without_size))
|
| 324 |
+
elif operation in _reductions or callable(operation):
|
| 325 |
+
difference = set.difference(rght.identifiers, left.identifiers)
|
| 326 |
+
if len(difference) > 0:
|
| 327 |
+
raise EinopsError("Unexpected identifiers on the right side of reduce {}: {}".format(operation, difference))
|
| 328 |
+
else:
|
| 329 |
+
raise EinopsError("Unknown reduction {}. Expect one of {}.".format(operation, _reductions))
|
| 330 |
+
|
| 331 |
+
if left.has_ellipsis:
|
| 332 |
+
n_other_dims = len(left.composition) - 1
|
| 333 |
+
if ndim < n_other_dims:
|
| 334 |
+
raise EinopsError(f"Wrong shape: expected >={n_other_dims} dims. Received {ndim}-dim tensor.")
|
| 335 |
+
ellipsis_ndim = ndim - n_other_dims
|
| 336 |
+
ell_axes = [_ellipsis + str(i) for i in range(ellipsis_ndim)]
|
| 337 |
+
left_composition = []
|
| 338 |
+
for composite_axis in left.composition:
|
| 339 |
+
if composite_axis == _ellipsis:
|
| 340 |
+
for axis in ell_axes:
|
| 341 |
+
left_composition.append([axis])
|
| 342 |
+
else:
|
| 343 |
+
left_composition.append(composite_axis)
|
| 344 |
+
|
| 345 |
+
rght_composition = []
|
| 346 |
+
for composite_axis in rght.composition:
|
| 347 |
+
if composite_axis == _ellipsis:
|
| 348 |
+
for axis in ell_axes:
|
| 349 |
+
rght_composition.append([axis])
|
| 350 |
+
else:
|
| 351 |
+
group = []
|
| 352 |
+
for axis in composite_axis:
|
| 353 |
+
if axis == _ellipsis:
|
| 354 |
+
group.extend(ell_axes)
|
| 355 |
+
else:
|
| 356 |
+
group.append(axis)
|
| 357 |
+
rght_composition.append(group)
|
| 358 |
+
|
| 359 |
+
left.identifiers.update(ell_axes)
|
| 360 |
+
left.identifiers.remove(_ellipsis)
|
| 361 |
+
if rght.has_ellipsis:
|
| 362 |
+
rght.identifiers.update(ell_axes)
|
| 363 |
+
rght.identifiers.remove(_ellipsis)
|
| 364 |
+
else:
|
| 365 |
+
if ndim != len(left.composition):
|
| 366 |
+
raise EinopsError(f"Wrong shape: expected {len(left.composition)} dims. Received {ndim}-dim tensor.")
|
| 367 |
+
left_composition = left.composition
|
| 368 |
+
rght_composition = rght.composition
|
| 369 |
+
|
| 370 |
+
# parsing all dimensions to find out lengths
|
| 371 |
+
axis_name2known_length: Dict[Union[str, AnonymousAxis], int] = OrderedDict()
|
| 372 |
+
for composite_axis in left_composition:
|
| 373 |
+
for axis_name in composite_axis:
|
| 374 |
+
if isinstance(axis_name, AnonymousAxis):
|
| 375 |
+
axis_name2known_length[axis_name] = axis_name.value
|
| 376 |
+
else:
|
| 377 |
+
axis_name2known_length[axis_name] = _unknown_axis_length
|
| 378 |
+
|
| 379 |
+
# axis_ids_after_first_reshape = range(len(axis_name2known_length)) at this point
|
| 380 |
+
|
| 381 |
+
repeat_axes_names = []
|
| 382 |
+
for axis_name in rght.identifiers:
|
| 383 |
+
if axis_name not in axis_name2known_length:
|
| 384 |
+
if isinstance(axis_name, AnonymousAxis):
|
| 385 |
+
axis_name2known_length[axis_name] = axis_name.value
|
| 386 |
+
else:
|
| 387 |
+
axis_name2known_length[axis_name] = _unknown_axis_length
|
| 388 |
+
repeat_axes_names.append(axis_name)
|
| 389 |
+
|
| 390 |
+
axis_name2position = {name: position for position, name in enumerate(axis_name2known_length)}
|
| 391 |
+
|
| 392 |
+
# axes provided as kwargs
|
| 393 |
+
for elementary_axis in axes_names:
|
| 394 |
+
if not ParsedExpression.check_axis_name(elementary_axis):
|
| 395 |
+
raise EinopsError("Invalid name for an axis", elementary_axis)
|
| 396 |
+
if elementary_axis not in axis_name2known_length:
|
| 397 |
+
raise EinopsError("Axis {} is not used in transform".format(elementary_axis))
|
| 398 |
+
axis_name2known_length[elementary_axis] = _expected_axis_length
|
| 399 |
+
|
| 400 |
+
input_axes_known_unknown = []
|
| 401 |
+
# some shapes are inferred later - all information is prepared for faster inference
|
| 402 |
+
for i, composite_axis in enumerate(left_composition):
|
| 403 |
+
known: Set[str] = {axis for axis in composite_axis if axis_name2known_length[axis] != _unknown_axis_length}
|
| 404 |
+
unknown: Set[str] = {axis for axis in composite_axis if axis_name2known_length[axis] == _unknown_axis_length}
|
| 405 |
+
if len(unknown) > 1:
|
| 406 |
+
raise EinopsError("Could not infer sizes for {}".format(unknown))
|
| 407 |
+
assert len(unknown) + len(known) == len(composite_axis)
|
| 408 |
+
input_axes_known_unknown.append(
|
| 409 |
+
([axis_name2position[axis] for axis in known], [axis_name2position[axis] for axis in unknown])
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
axis_position_after_reduction: Dict[str, int] = {}
|
| 413 |
+
for axis_name in itertools.chain(*left_composition):
|
| 414 |
+
if axis_name in rght.identifiers:
|
| 415 |
+
axis_position_after_reduction[axis_name] = len(axis_position_after_reduction)
|
| 416 |
+
|
| 417 |
+
result_axes_grouping: List[List[int]] = [
|
| 418 |
+
[axis_name2position[axis] for axis in composite_axis] for i, composite_axis in enumerate(rght_composition)
|
| 419 |
+
]
|
| 420 |
+
|
| 421 |
+
ordered_axis_left = list(itertools.chain(*left_composition))
|
| 422 |
+
ordered_axis_rght = list(itertools.chain(*rght_composition))
|
| 423 |
+
reduced_axes = [axis for axis in ordered_axis_left if axis not in rght.identifiers]
|
| 424 |
+
order_after_transposition = [axis for axis in ordered_axis_rght if axis in left.identifiers] + reduced_axes
|
| 425 |
+
axes_permutation = [ordered_axis_left.index(axis) for axis in order_after_transposition]
|
| 426 |
+
added_axes = {
|
| 427 |
+
i: axis_name2position[axis_name]
|
| 428 |
+
for i, axis_name in enumerate(ordered_axis_rght)
|
| 429 |
+
if axis_name not in left.identifiers
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
first_reduced_axis = len(order_after_transposition) - len(reduced_axes)
|
| 433 |
+
|
| 434 |
+
return TransformRecipe(
|
| 435 |
+
elementary_axes_lengths=list(axis_name2known_length.values()),
|
| 436 |
+
axis_name2elementary_axis={axis: axis_name2position[axis] for axis in axes_names},
|
| 437 |
+
input_composition_known_unknown=input_axes_known_unknown,
|
| 438 |
+
axes_permutation=axes_permutation,
|
| 439 |
+
first_reduced_axis=first_reduced_axis,
|
| 440 |
+
added_axes=added_axes,
|
| 441 |
+
output_composite_axes=result_axes_grouping,
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def _prepare_recipes_for_all_dims(
|
| 446 |
+
pattern: str, operation: Reduction, axes_names: Tuple[str, ...]
|
| 447 |
+
) -> Dict[int, TransformRecipe]:
|
| 448 |
+
"""
|
| 449 |
+
Internal function, used in layers.
|
| 450 |
+
Layer makes all recipe creation when it is initialized, thus to keep recipes simple we pre-compute for all dims
|
| 451 |
+
"""
|
| 452 |
+
left_str, rght_str = pattern.split("->")
|
| 453 |
+
left = ParsedExpression(left_str)
|
| 454 |
+
dims = [len(left.composition)]
|
| 455 |
+
if left.has_ellipsis:
|
| 456 |
+
dims = [len(left.composition) - 1 + ellipsis_dims for ellipsis_dims in range(8)]
|
| 457 |
+
return {ndim: _prepare_transformation_recipe(pattern, operation, axes_names, ndim=ndim) for ndim in dims}
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def reduce(tensor: Union[Tensor, List[Tensor]], pattern: str, reduction: Reduction, **axes_lengths: Size) -> Tensor:
|
| 461 |
+
"""
|
| 462 |
+
einops.reduce combines rearrangement and reduction using reader-friendly notation.
|
| 463 |
+
|
| 464 |
+
Some examples:
|
| 465 |
+
|
| 466 |
+
```python
|
| 467 |
+
>>> x = np.random.randn(100, 32, 64)
|
| 468 |
+
|
| 469 |
+
# perform max-reduction on the first axis
|
| 470 |
+
# Axis t does not appear on RHS - thus we reduced over t
|
| 471 |
+
>>> y = reduce(x, 't b c -> b c', 'max')
|
| 472 |
+
|
| 473 |
+
# same as previous, but using verbose names for axes
|
| 474 |
+
>>> y = reduce(x, 'time batch channel -> batch channel', 'max')
|
| 475 |
+
|
| 476 |
+
# let's pretend now that x is a batch of images
|
| 477 |
+
# with 4 dims: batch=10, height=20, width=30, channel=40
|
| 478 |
+
>>> x = np.random.randn(10, 20, 30, 40)
|
| 479 |
+
|
| 480 |
+
# 2d max-pooling with kernel size = 2 * 2 for image processing
|
| 481 |
+
>>> y1 = reduce(x, 'b c (h1 h2) (w1 w2) -> b c h1 w1', 'max', h2=2, w2=2)
|
| 482 |
+
|
| 483 |
+
# same as previous, using anonymous axes,
|
| 484 |
+
# note: only reduced axes can be anonymous
|
| 485 |
+
>>> y1 = reduce(x, 'b c (h1 2) (w1 2) -> b c h1 w1', 'max')
|
| 486 |
+
|
| 487 |
+
# adaptive 2d max-pooling to 3 * 4 grid,
|
| 488 |
+
# each element is max of 10x10 tile in the original tensor.
|
| 489 |
+
>>> reduce(x, 'b c (h1 h2) (w1 w2) -> b c h1 w1', 'max', h1=3, w1=4).shape
|
| 490 |
+
(10, 20, 3, 4)
|
| 491 |
+
|
| 492 |
+
# Global average pooling
|
| 493 |
+
>>> reduce(x, 'b c h w -> b c', 'mean').shape
|
| 494 |
+
(10, 20)
|
| 495 |
+
|
| 496 |
+
# subtracting mean over batch for each channel;
|
| 497 |
+
# similar to x - np.mean(x, axis=(0, 2, 3), keepdims=True)
|
| 498 |
+
>>> y = x - reduce(x, 'b c h w -> 1 c 1 1', 'mean')
|
| 499 |
+
|
| 500 |
+
# Subtracting per-image mean for each channel
|
| 501 |
+
>>> y = x - reduce(x, 'b c h w -> b c 1 1', 'mean')
|
| 502 |
+
|
| 503 |
+
# same as previous, but using empty compositions
|
| 504 |
+
>>> y = x - reduce(x, 'b c h w -> b c () ()', 'mean')
|
| 505 |
+
|
| 506 |
+
```
|
| 507 |
+
|
| 508 |
+
Parameters:
|
| 509 |
+
tensor: tensor: tensor of any supported library (e.g. numpy.ndarray, tensorflow, pytorch).
|
| 510 |
+
list of tensors is also accepted, those should be of the same type and shape
|
| 511 |
+
pattern: string, reduction pattern
|
| 512 |
+
reduction: one of available reductions ('min', 'max', 'sum', 'mean', 'prod', 'any', 'all').
|
| 513 |
+
Alternatively, a callable f(tensor, reduced_axes) -> tensor can be provided.
|
| 514 |
+
This allows using various reductions like: np.max, np.nanmean, tf.reduce_logsumexp, torch.var, etc.
|
| 515 |
+
axes_lengths: any additional specifications for dimensions
|
| 516 |
+
|
| 517 |
+
Returns:
|
| 518 |
+
tensor of the same type as input
|
| 519 |
+
"""
|
| 520 |
+
try:
|
| 521 |
+
if isinstance(tensor, list):
|
| 522 |
+
if len(tensor) == 0:
|
| 523 |
+
raise TypeError("Rearrange/Reduce/Repeat can't be applied to an empty list")
|
| 524 |
+
backend = get_backend(tensor[0])
|
| 525 |
+
tensor = backend.stack_on_zeroth_dimension(tensor)
|
| 526 |
+
else:
|
| 527 |
+
backend = get_backend(tensor)
|
| 528 |
+
|
| 529 |
+
hashable_axes_lengths = tuple(axes_lengths.items())
|
| 530 |
+
shape = backend.shape(tensor)
|
| 531 |
+
recipe = _prepare_transformation_recipe(pattern, reduction, axes_names=tuple(axes_lengths), ndim=len(shape))
|
| 532 |
+
return _apply_recipe(
|
| 533 |
+
backend, recipe, cast(Tensor, tensor), reduction_type=reduction, axes_lengths=hashable_axes_lengths
|
| 534 |
+
)
|
| 535 |
+
except EinopsError as e:
|
| 536 |
+
message = ' Error while processing {}-reduction pattern "{}".'.format(reduction, pattern)
|
| 537 |
+
if not isinstance(tensor, list):
|
| 538 |
+
message += "\n Input tensor shape: {}. ".format(shape)
|
| 539 |
+
else:
|
| 540 |
+
message += "\n Input is list. "
|
| 541 |
+
message += "Additional info: {}.".format(axes_lengths)
|
| 542 |
+
raise EinopsError(message + "\n {}".format(e))
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
def rearrange(tensor: Union[Tensor, List[Tensor]], pattern: str, **axes_lengths: Size) -> Tensor:
|
| 546 |
+
"""
|
| 547 |
+
einops.rearrange is a reader-friendly smart element reordering for multidimensional tensors.
|
| 548 |
+
This operation includes functionality of transpose (axes permutation), reshape (view), squeeze, unsqueeze,
|
| 549 |
+
stack, concatenate and other operations.
|
| 550 |
+
|
| 551 |
+
Examples:
|
| 552 |
+
|
| 553 |
+
```python
|
| 554 |
+
# suppose we have a set of 32 images in "h w c" format (height-width-channel)
|
| 555 |
+
>>> images = [np.random.randn(30, 40, 3) for _ in range(32)]
|
| 556 |
+
|
| 557 |
+
# stack along first (batch) axis, output is a single array
|
| 558 |
+
>>> rearrange(images, 'b h w c -> b h w c').shape
|
| 559 |
+
(32, 30, 40, 3)
|
| 560 |
+
|
| 561 |
+
# stacked and reordered axes to "b c h w" format
|
| 562 |
+
>>> rearrange(images, 'b h w c -> b c h w').shape
|
| 563 |
+
(32, 3, 30, 40)
|
| 564 |
+
|
| 565 |
+
# concatenate images along height (vertical axis), 960 = 32 * 30
|
| 566 |
+
>>> rearrange(images, 'b h w c -> (b h) w c').shape
|
| 567 |
+
(960, 40, 3)
|
| 568 |
+
|
| 569 |
+
# concatenated images along horizontal axis, 1280 = 32 * 40
|
| 570 |
+
>>> rearrange(images, 'b h w c -> h (b w) c').shape
|
| 571 |
+
(30, 1280, 3)
|
| 572 |
+
|
| 573 |
+
# flattened each image into a vector, 3600 = 30 * 40 * 3
|
| 574 |
+
>>> rearrange(images, 'b h w c -> b (c h w)').shape
|
| 575 |
+
(32, 3600)
|
| 576 |
+
|
| 577 |
+
# split each image into 4 smaller (top-left, top-right, bottom-left, bottom-right), 128 = 32 * 2 * 2
|
| 578 |
+
>>> rearrange(images, 'b (h1 h) (w1 w) c -> (b h1 w1) h w c', h1=2, w1=2).shape
|
| 579 |
+
(128, 15, 20, 3)
|
| 580 |
+
|
| 581 |
+
# space-to-depth operation
|
| 582 |
+
>>> rearrange(images, 'b (h h1) (w w1) c -> b h w (c h1 w1)', h1=2, w1=2).shape
|
| 583 |
+
(32, 15, 20, 12)
|
| 584 |
+
|
| 585 |
+
```
|
| 586 |
+
|
| 587 |
+
When composing axes, C-order enumeration used (consecutive elements have different last axis).
|
| 588 |
+
Find more examples in einops tutorial.
|
| 589 |
+
|
| 590 |
+
Parameters:
|
| 591 |
+
tensor: tensor of any supported library (e.g. numpy.ndarray, tensorflow, pytorch).
|
| 592 |
+
list of tensors is also accepted, those should be of the same type and shape
|
| 593 |
+
pattern: string, rearrangement pattern
|
| 594 |
+
axes_lengths: any additional specifications for dimensions
|
| 595 |
+
|
| 596 |
+
Returns:
|
| 597 |
+
tensor of the same type as input. If possible, a view to the original tensor is returned.
|
| 598 |
+
|
| 599 |
+
"""
|
| 600 |
+
return reduce(tensor, pattern, reduction="rearrange", **axes_lengths)
|
| 601 |
+
|
| 602 |
+
|
| 603 |
+
def repeat(tensor: Union[Tensor, List[Tensor]], pattern: str, **axes_lengths: Size) -> Tensor:
|
| 604 |
+
"""
|
| 605 |
+
einops.repeat allows reordering elements and repeating them in arbitrary combinations.
|
| 606 |
+
This operation includes functionality of repeat, tile, and broadcast functions.
|
| 607 |
+
|
| 608 |
+
Examples for repeat operation:
|
| 609 |
+
|
| 610 |
+
```python
|
| 611 |
+
# a grayscale image (of shape height x width)
|
| 612 |
+
>>> image = np.random.randn(30, 40)
|
| 613 |
+
|
| 614 |
+
# change it to RGB format by repeating in each channel
|
| 615 |
+
>>> repeat(image, 'h w -> h w c', c=3).shape
|
| 616 |
+
(30, 40, 3)
|
| 617 |
+
|
| 618 |
+
# repeat image 2 times along height (vertical axis)
|
| 619 |
+
>>> repeat(image, 'h w -> (repeat h) w', repeat=2).shape
|
| 620 |
+
(60, 40)
|
| 621 |
+
|
| 622 |
+
# repeat image 2 time along height and 3 times along width
|
| 623 |
+
>>> repeat(image, 'h w -> (h2 h) (w3 w)', h2=2, w3=3).shape
|
| 624 |
+
(60, 120)
|
| 625 |
+
|
| 626 |
+
# convert each pixel to a small square 2x2. Upsample image by 2x
|
| 627 |
+
>>> repeat(image, 'h w -> (h h2) (w w2)', h2=2, w2=2).shape
|
| 628 |
+
(60, 80)
|
| 629 |
+
|
| 630 |
+
# pixelate image first by downsampling by 2x, then upsampling
|
| 631 |
+
>>> downsampled = reduce(image, '(h h2) (w w2) -> h w', 'mean', h2=2, w2=2)
|
| 632 |
+
>>> repeat(downsampled, 'h w -> (h h2) (w w2)', h2=2, w2=2).shape
|
| 633 |
+
(30, 40)
|
| 634 |
+
|
| 635 |
+
```
|
| 636 |
+
|
| 637 |
+
When composing axes, C-order enumeration used (consecutive elements have different last axis).
|
| 638 |
+
Find more examples in einops tutorial.
|
| 639 |
+
|
| 640 |
+
Parameters:
|
| 641 |
+
tensor: tensor of any supported library (e.g. numpy.ndarray, tensorflow, pytorch).
|
| 642 |
+
list of tensors is also accepted, those should be of the same type and shape
|
| 643 |
+
pattern: string, rearrangement pattern
|
| 644 |
+
axes_lengths: any additional specifications for dimensions
|
| 645 |
+
|
| 646 |
+
Returns:
|
| 647 |
+
Tensor of the same type as input. If possible, a view to the original tensor is returned.
|
| 648 |
+
|
| 649 |
+
"""
|
| 650 |
+
return reduce(tensor, pattern, reduction="repeat", **axes_lengths)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
def parse_shape(x: Tensor, pattern: str) -> dict:
|
| 654 |
+
"""
|
| 655 |
+
Parse a tensor shape to dictionary mapping axes names to their lengths.
|
| 656 |
+
|
| 657 |
+
```python
|
| 658 |
+
# Use underscore to skip the dimension in parsing.
|
| 659 |
+
>>> x = np.zeros([2, 3, 5, 7])
|
| 660 |
+
>>> parse_shape(x, 'batch _ h w')
|
| 661 |
+
{'batch': 2, 'h': 5, 'w': 7}
|
| 662 |
+
|
| 663 |
+
# `parse_shape` output can be used to specify axes_lengths for other operations:
|
| 664 |
+
>>> y = np.zeros([700])
|
| 665 |
+
>>> rearrange(y, '(b c h w) -> b c h w', **parse_shape(x, 'b _ h w')).shape
|
| 666 |
+
(2, 10, 5, 7)
|
| 667 |
+
|
| 668 |
+
```
|
| 669 |
+
|
| 670 |
+
For symbolic frameworks may return symbols, not integers.
|
| 671 |
+
|
| 672 |
+
Parameters:
|
| 673 |
+
x: tensor of any supported framework
|
| 674 |
+
pattern: str, space separated names for axes, underscore means skip axis
|
| 675 |
+
|
| 676 |
+
Returns:
|
| 677 |
+
dict, maps axes names to their lengths
|
| 678 |
+
"""
|
| 679 |
+
exp = ParsedExpression(pattern, allow_underscore=True)
|
| 680 |
+
shape = get_backend(x).shape(x)
|
| 681 |
+
if exp.has_composed_axes():
|
| 682 |
+
raise RuntimeError(f"Can't parse shape with composite axes: {pattern} {shape}")
|
| 683 |
+
if len(shape) != len(exp.composition):
|
| 684 |
+
if exp.has_ellipsis:
|
| 685 |
+
if len(shape) < len(exp.composition) - 1:
|
| 686 |
+
raise RuntimeError(f"Can't parse shape with this number of dimensions: {pattern} {shape}")
|
| 687 |
+
else:
|
| 688 |
+
raise RuntimeError(f"Can't parse shape with different number of dimensions: {pattern} {shape}")
|
| 689 |
+
if exp.has_ellipsis:
|
| 690 |
+
ellipsis_idx = exp.composition.index(_ellipsis)
|
| 691 |
+
composition = (
|
| 692 |
+
exp.composition[:ellipsis_idx]
|
| 693 |
+
+ ["_"] * (len(shape) - len(exp.composition) + 1)
|
| 694 |
+
+ exp.composition[ellipsis_idx + 1 :]
|
| 695 |
+
)
|
| 696 |
+
else:
|
| 697 |
+
composition = exp.composition
|
| 698 |
+
result = {}
|
| 699 |
+
for axes, axis_length in zip(composition, shape): # type: ignore
|
| 700 |
+
# axes either [], or [AnonymousAxis] or ['axis_name']
|
| 701 |
+
if len(axes) == 0:
|
| 702 |
+
if axis_length != 1:
|
| 703 |
+
raise RuntimeError(f"Length of axis is not 1: {pattern} {shape}")
|
| 704 |
+
else:
|
| 705 |
+
[axis] = axes
|
| 706 |
+
if isinstance(axis, str):
|
| 707 |
+
if axis != "_":
|
| 708 |
+
result[axis] = axis_length
|
| 709 |
+
else:
|
| 710 |
+
if axis.value != axis_length:
|
| 711 |
+
raise RuntimeError(f"Length of anonymous axis does not match: {pattern} {shape}")
|
| 712 |
+
return result
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
# _enumerate_directions is not exposed in the public API
|
| 716 |
+
def _enumerate_directions(x):
|
| 717 |
+
"""
|
| 718 |
+
For an n-dimensional tensor, returns tensors to enumerate each axis.
|
| 719 |
+
```python
|
| 720 |
+
x = np.zeros([2, 3, 4]) # or any other tensor
|
| 721 |
+
i, j, k = _enumerate_directions(x)
|
| 722 |
+
result = i + 2*j + 3*k
|
| 723 |
+
```
|
| 724 |
+
|
| 725 |
+
`result[i, j, k] = i + 2j + 3k`, and also has the same shape as result
|
| 726 |
+
Works very similarly to numpy.ogrid (open indexing grid)
|
| 727 |
+
"""
|
| 728 |
+
backend = get_backend(x)
|
| 729 |
+
shape = backend.shape(x)
|
| 730 |
+
result = []
|
| 731 |
+
for axis_id, axis_length in enumerate(shape):
|
| 732 |
+
shape = [1] * len(shape)
|
| 733 |
+
shape[axis_id] = axis_length
|
| 734 |
+
result.append(backend.reshape(backend.arange(0, axis_length), shape))
|
| 735 |
+
return result
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
# to avoid importing numpy
|
| 739 |
+
np_ndarray = Any
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
def asnumpy(tensor: Tensor) -> np_ndarray:
|
| 743 |
+
"""
|
| 744 |
+
Convert a tensor of an imperative framework (i.e. numpy/cupy/torch/jax/etc.) to `numpy.ndarray`
|
| 745 |
+
|
| 746 |
+
Parameters:
|
| 747 |
+
tensor: tensor of any known imperative framework
|
| 748 |
+
|
| 749 |
+
Returns:
|
| 750 |
+
`numpy.ndarray`, converted to numpy
|
| 751 |
+
"""
|
| 752 |
+
return get_backend(tensor).to_numpy(tensor)
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
def _validate_einsum_axis_name(axis_name):
|
| 756 |
+
if len(axis_name) == 0:
|
| 757 |
+
raise NotImplementedError("Singleton () axes are not yet supported in einsum.")
|
| 758 |
+
if len(axis_name) > 1:
|
| 759 |
+
raise NotImplementedError("Shape rearrangement is not yet supported in einsum.")
|
| 760 |
+
|
| 761 |
+
axis_name = axis_name[0]
|
| 762 |
+
|
| 763 |
+
if isinstance(axis_name, AnonymousAxis):
|
| 764 |
+
raise NotImplementedError("Anonymous axes are not yet supported in einsum.")
|
| 765 |
+
if len(axis_name) == 0:
|
| 766 |
+
raise RuntimeError("Encountered empty axis name in einsum.")
|
| 767 |
+
if not isinstance(axis_name, str):
|
| 768 |
+
raise RuntimeError("Axis name in einsum must be a string.")
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
@functools.lru_cache(256)
|
| 772 |
+
def _compactify_pattern_for_einsum(pattern: str) -> str:
|
| 773 |
+
if "->" not in pattern:
|
| 774 |
+
# numpy allows this, so make sure users
|
| 775 |
+
# don't accidentally do something like this.
|
| 776 |
+
raise ValueError("Einsum pattern must contain '->'.")
|
| 777 |
+
lefts_str, right_str = pattern.split("->")
|
| 778 |
+
|
| 779 |
+
lefts = [ParsedExpression(left, allow_underscore=True, allow_duplicates=True) for left in lefts_str.split(",")]
|
| 780 |
+
|
| 781 |
+
right = ParsedExpression(right_str, allow_underscore=True)
|
| 782 |
+
|
| 783 |
+
# Start from 'a' and go up to 'Z'
|
| 784 |
+
output_axis_names = string.ascii_letters
|
| 785 |
+
i = 0
|
| 786 |
+
axis_name_mapping = {}
|
| 787 |
+
|
| 788 |
+
left_patterns = []
|
| 789 |
+
for left in lefts:
|
| 790 |
+
left_pattern = ""
|
| 791 |
+
for raw_axis_name in left.composition:
|
| 792 |
+
if raw_axis_name == _ellipsis:
|
| 793 |
+
left_pattern += "..."
|
| 794 |
+
continue
|
| 795 |
+
|
| 796 |
+
_validate_einsum_axis_name(raw_axis_name)
|
| 797 |
+
axis_name = raw_axis_name[0]
|
| 798 |
+
if axis_name not in axis_name_mapping:
|
| 799 |
+
if i >= len(output_axis_names):
|
| 800 |
+
raise RuntimeError("Too many axes in einsum.")
|
| 801 |
+
axis_name_mapping[axis_name] = output_axis_names[i]
|
| 802 |
+
i += 1
|
| 803 |
+
|
| 804 |
+
left_pattern += axis_name_mapping[axis_name]
|
| 805 |
+
left_patterns.append(left_pattern)
|
| 806 |
+
|
| 807 |
+
compact_pattern = ",".join(left_patterns) + "->"
|
| 808 |
+
|
| 809 |
+
for raw_axis_name in right.composition:
|
| 810 |
+
if raw_axis_name == _ellipsis:
|
| 811 |
+
compact_pattern += "..."
|
| 812 |
+
continue
|
| 813 |
+
|
| 814 |
+
_validate_einsum_axis_name(raw_axis_name)
|
| 815 |
+
axis_name = raw_axis_name[0]
|
| 816 |
+
|
| 817 |
+
if axis_name not in axis_name_mapping:
|
| 818 |
+
raise EinopsError(f"Unknown axis {axis_name} on right side of einsum {pattern}.")
|
| 819 |
+
|
| 820 |
+
compact_pattern += axis_name_mapping[axis_name]
|
| 821 |
+
|
| 822 |
+
return compact_pattern
|
| 823 |
+
|
| 824 |
+
|
| 825 |
+
@typing.overload
|
| 826 |
+
def einsum(tensor: Tensor, pattern: str, /) -> Tensor: ...
|
| 827 |
+
|
| 828 |
+
|
| 829 |
+
@typing.overload
|
| 830 |
+
def einsum(tensor1: Tensor, tensor2: Tensor, pattern: str, /) -> Tensor: ...
|
| 831 |
+
|
| 832 |
+
|
| 833 |
+
@typing.overload
|
| 834 |
+
def einsum(tensor1: Tensor, tensor2: Tensor, tensor3: Tensor, pattern: str, /) -> Tensor: ...
|
| 835 |
+
|
| 836 |
+
|
| 837 |
+
@typing.overload
|
| 838 |
+
def einsum(tensor1: Tensor, tensor2: Tensor, tensor3: Tensor, tensor4: Tensor, pattern: str, /) -> Tensor: ...
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
def einsum(*tensors_and_pattern: Union[Tensor, str]) -> Tensor:
|
| 842 |
+
r"""
|
| 843 |
+
einops.einsum calls einsum operations with einops-style named
|
| 844 |
+
axes indexing, computing tensor products with an arbitrary
|
| 845 |
+
number of tensors. Unlike typical einsum syntax, here you must
|
| 846 |
+
pass tensors first, and then the pattern.
|
| 847 |
+
|
| 848 |
+
Also, note that rearrange operations such as `"(batch chan) out"`,
|
| 849 |
+
or singleton axes `()`, are not currently supported.
|
| 850 |
+
|
| 851 |
+
Examples:
|
| 852 |
+
|
| 853 |
+
For a given pattern such as:
|
| 854 |
+
```python
|
| 855 |
+
>>> x, y, z = np.random.randn(3, 20, 20, 20)
|
| 856 |
+
>>> output = einsum(x, y, z, "a b c, c b d, a g k -> a b k")
|
| 857 |
+
|
| 858 |
+
```
|
| 859 |
+
the following formula is computed:
|
| 860 |
+
```tex
|
| 861 |
+
output[a, b, k] =
|
| 862 |
+
\sum_{c, d, g} x[a, b, c] * y[c, b, d] * z[a, g, k]
|
| 863 |
+
```
|
| 864 |
+
where the summation over `c`, `d`, and `g` is performed
|
| 865 |
+
because those axes names do not appear on the right-hand side.
|
| 866 |
+
|
| 867 |
+
Let's see some additional examples:
|
| 868 |
+
```python
|
| 869 |
+
# Filter a set of images:
|
| 870 |
+
>>> batched_images = np.random.randn(128, 16, 16)
|
| 871 |
+
>>> filters = np.random.randn(16, 16, 30)
|
| 872 |
+
>>> result = einsum(batched_images, filters,
|
| 873 |
+
... "batch h w, h w channel -> batch channel")
|
| 874 |
+
>>> result.shape
|
| 875 |
+
(128, 30)
|
| 876 |
+
|
| 877 |
+
# Matrix multiplication, with an unknown input shape:
|
| 878 |
+
>>> batch_shape = (50, 30)
|
| 879 |
+
>>> data = np.random.randn(*batch_shape, 20)
|
| 880 |
+
>>> weights = np.random.randn(10, 20)
|
| 881 |
+
>>> result = einsum(weights, data,
|
| 882 |
+
... "out_dim in_dim, ... in_dim -> ... out_dim")
|
| 883 |
+
>>> result.shape
|
| 884 |
+
(50, 30, 10)
|
| 885 |
+
|
| 886 |
+
# Matrix trace on a single tensor:
|
| 887 |
+
>>> matrix = np.random.randn(10, 10)
|
| 888 |
+
>>> result = einsum(matrix, "i i ->")
|
| 889 |
+
>>> result.shape
|
| 890 |
+
()
|
| 891 |
+
|
| 892 |
+
```
|
| 893 |
+
|
| 894 |
+
Parameters:
|
| 895 |
+
tensors_and_pattern:
|
| 896 |
+
tensors: tensors of any supported library (numpy, tensorflow, pytorch, jax).
|
| 897 |
+
pattern: string, einsum pattern, with commas
|
| 898 |
+
separating specifications for each tensor.
|
| 899 |
+
pattern should be provided after all tensors.
|
| 900 |
+
|
| 901 |
+
Returns:
|
| 902 |
+
Tensor of the same type as input, after processing with einsum.
|
| 903 |
+
|
| 904 |
+
"""
|
| 905 |
+
if len(tensors_and_pattern) <= 1:
|
| 906 |
+
raise ValueError(
|
| 907 |
+
"`einops.einsum` takes at minimum two arguments: the tensors (at least one), followed by the pattern."
|
| 908 |
+
)
|
| 909 |
+
pattern = tensors_and_pattern[-1]
|
| 910 |
+
if not isinstance(pattern, str):
|
| 911 |
+
raise ValueError(
|
| 912 |
+
"The last argument passed to `einops.einsum` must be a string, representing the einsum pattern."
|
| 913 |
+
)
|
| 914 |
+
tensors = tensors_and_pattern[:-1]
|
| 915 |
+
pattern = _compactify_pattern_for_einsum(pattern)
|
| 916 |
+
return get_backend(tensors[0]).einsum(pattern, *tensors)
|
build/torch-cuda/experimental/__init__.py
ADDED
|
File without changes
|
build/torch-cuda/experimental/indexing.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This file contained some thoughts on indexing.
|
| 3 |
+
|
| 4 |
+
These ideas were developed further in eindex (separate package).
|
| 5 |
+
"""
|
build/torch-cuda/layers/__init__.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__author__ = "Alex Rogozhnikov"
|
| 2 |
+
|
| 3 |
+
from typing import Any, Dict
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
from ..einops import TransformRecipe, _apply_recipe, _prepare_recipes_for_all_dims, get_backend
|
| 7 |
+
from .. import EinopsError
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class RearrangeMixin:
|
| 11 |
+
"""
|
| 12 |
+
Rearrange layer behaves identically to einops.rearrange operation.
|
| 13 |
+
|
| 14 |
+
:param pattern: str, rearrangement pattern
|
| 15 |
+
:param axes_lengths: any additional specification of dimensions
|
| 16 |
+
|
| 17 |
+
See einops.rearrange for source_examples.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, pattern: str, **axes_lengths: Any) -> None:
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.pattern = pattern
|
| 23 |
+
self.axes_lengths = axes_lengths
|
| 24 |
+
# self._recipe = self.recipe() # checking parameters
|
| 25 |
+
self._multirecipe = self.multirecipe()
|
| 26 |
+
self._axes_lengths = tuple(self.axes_lengths.items())
|
| 27 |
+
|
| 28 |
+
def __repr__(self) -> str:
|
| 29 |
+
params = repr(self.pattern)
|
| 30 |
+
for axis, length in self.axes_lengths.items():
|
| 31 |
+
params += ", {}={}".format(axis, length)
|
| 32 |
+
return "{}({})".format(self.__class__.__name__, params)
|
| 33 |
+
|
| 34 |
+
def multirecipe(self) -> Dict[int, TransformRecipe]:
|
| 35 |
+
try:
|
| 36 |
+
return _prepare_recipes_for_all_dims(
|
| 37 |
+
self.pattern, operation="rearrange", axes_names=tuple(self.axes_lengths)
|
| 38 |
+
)
|
| 39 |
+
except EinopsError as e:
|
| 40 |
+
raise EinopsError(" Error while preparing {!r}\n {}".format(self, e))
|
| 41 |
+
|
| 42 |
+
def _apply_recipe(self, x):
|
| 43 |
+
backend = get_backend(x)
|
| 44 |
+
return _apply_recipe(
|
| 45 |
+
backend=backend,
|
| 46 |
+
recipe=self._multirecipe[len(x.shape)],
|
| 47 |
+
tensor=x,
|
| 48 |
+
reduction_type="rearrange",
|
| 49 |
+
axes_lengths=self._axes_lengths,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def __getstate__(self):
|
| 53 |
+
return {"pattern": self.pattern, "axes_lengths": self.axes_lengths}
|
| 54 |
+
|
| 55 |
+
def __setstate__(self, state):
|
| 56 |
+
self.__init__(pattern=state["pattern"], **state["axes_lengths"])
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class ReduceMixin:
|
| 60 |
+
"""
|
| 61 |
+
Reduce layer behaves identically to einops.reduce operation.
|
| 62 |
+
|
| 63 |
+
:param pattern: str, rearrangement pattern
|
| 64 |
+
:param reduction: one of available reductions ('min', 'max', 'sum', 'mean', 'prod'), case-sensitive
|
| 65 |
+
:param axes_lengths: any additional specification of dimensions
|
| 66 |
+
|
| 67 |
+
See einops.reduce for source_examples.
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
def __init__(self, pattern: str, reduction: str, **axes_lengths: Any):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.pattern = pattern
|
| 73 |
+
self.reduction = reduction
|
| 74 |
+
self.axes_lengths = axes_lengths
|
| 75 |
+
self._multirecipe = self.multirecipe()
|
| 76 |
+
self._axes_lengths = tuple(self.axes_lengths.items())
|
| 77 |
+
|
| 78 |
+
def __repr__(self):
|
| 79 |
+
params = "{!r}, {!r}".format(self.pattern, self.reduction)
|
| 80 |
+
for axis, length in self.axes_lengths.items():
|
| 81 |
+
params += ", {}={}".format(axis, length)
|
| 82 |
+
return "{}({})".format(self.__class__.__name__, params)
|
| 83 |
+
|
| 84 |
+
def multirecipe(self) -> Dict[int, TransformRecipe]:
|
| 85 |
+
try:
|
| 86 |
+
return _prepare_recipes_for_all_dims(
|
| 87 |
+
self.pattern, operation=self.reduction, axes_names=tuple(self.axes_lengths)
|
| 88 |
+
)
|
| 89 |
+
except EinopsError as e:
|
| 90 |
+
raise EinopsError(" Error while preparing {!r}\n {}".format(self, e))
|
| 91 |
+
|
| 92 |
+
def _apply_recipe(self, x):
|
| 93 |
+
backend = get_backend(x)
|
| 94 |
+
return _apply_recipe(
|
| 95 |
+
backend=backend,
|
| 96 |
+
recipe=self._multirecipe[len(x.shape)],
|
| 97 |
+
tensor=x,
|
| 98 |
+
reduction_type=self.reduction,
|
| 99 |
+
axes_lengths=self._axes_lengths,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
def __getstate__(self):
|
| 103 |
+
return {"pattern": self.pattern, "reduction": self.reduction, "axes_lengths": self.axes_lengths}
|
| 104 |
+
|
| 105 |
+
def __setstate__(self, state):
|
| 106 |
+
self.__init__(pattern=state["pattern"], reduction=state["reduction"], **state["axes_lengths"])
|
build/torch-cuda/layers/_einmix.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, List, Optional, Dict
|
| 2 |
+
|
| 3 |
+
from .. import EinopsError
|
| 4 |
+
from ..parsing import ParsedExpression, _ellipsis
|
| 5 |
+
import warnings
|
| 6 |
+
import string
|
| 7 |
+
from ..einops import _product
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _report_axes(axes: set, report_message: str):
|
| 11 |
+
if len(axes) > 0:
|
| 12 |
+
raise EinopsError(report_message.format(axes))
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class _EinmixMixin:
|
| 16 |
+
def __init__(self, pattern: str, weight_shape: str, bias_shape: Optional[str] = None, **axes_lengths: Any):
|
| 17 |
+
"""
|
| 18 |
+
EinMix - Einstein summation with automated tensor management and axis packing/unpacking.
|
| 19 |
+
|
| 20 |
+
EinMix is a combination of einops and MLP, see tutorial:
|
| 21 |
+
https://github.com/arogozhnikov/einops/blob/main/docs/3-einmix-layer.ipynb
|
| 22 |
+
|
| 23 |
+
Imagine taking einsum with two arguments, one of each input, and one - tensor with weights
|
| 24 |
+
>>> einsum('time batch channel_in, channel_in channel_out -> time batch channel_out', input, weight)
|
| 25 |
+
|
| 26 |
+
This layer manages weights for you, syntax highlights a special role of weight matrix
|
| 27 |
+
>>> EinMix('time batch channel_in -> time batch channel_out', weight_shape='channel_in channel_out')
|
| 28 |
+
But otherwise it is the same einsum under the hood. Plus einops-rearrange.
|
| 29 |
+
|
| 30 |
+
Simple linear layer with a bias term (you have one like that in your framework)
|
| 31 |
+
>>> EinMix('t b cin -> t b cout', weight_shape='cin cout', bias_shape='cout', cin=10, cout=20)
|
| 32 |
+
There is no restriction to mix the last axis. Let's mix along height
|
| 33 |
+
>>> EinMix('h w c-> hout w c', weight_shape='h hout', bias_shape='hout', h=32, hout=32)
|
| 34 |
+
Example of channel-wise multiplication (like one used in normalizations)
|
| 35 |
+
>>> EinMix('t b c -> t b c', weight_shape='c', c=128)
|
| 36 |
+
Multi-head linear layer (each head is own linear layer):
|
| 37 |
+
>>> EinMix('t b (head cin) -> t b (head cout)', weight_shape='head cin cout', ...)
|
| 38 |
+
|
| 39 |
+
... and yes, you need to specify all dimensions of weight shape/bias shape in parameters.
|
| 40 |
+
|
| 41 |
+
Use cases:
|
| 42 |
+
- when channel dimension is not last, use EinMix, not transposition
|
| 43 |
+
- patch/segment embeddings
|
| 44 |
+
- when need only within-group connections to reduce number of weights and computations
|
| 45 |
+
- next-gen MLPs (follow tutorial link above to learn more!)
|
| 46 |
+
- in general, any time you want to combine linear layer and einops.rearrange
|
| 47 |
+
|
| 48 |
+
Uniform He initialization is applied to weight tensor.
|
| 49 |
+
This accounts for the number of elements mixed and produced.
|
| 50 |
+
|
| 51 |
+
Parameters
|
| 52 |
+
:param pattern: transformation pattern, left side - dimensions of input, right side - dimensions of output
|
| 53 |
+
:param weight_shape: axes of weight. A tensor of this shape is created, stored, and optimized in a layer
|
| 54 |
+
If bias_shape is not specified, bias is not created.
|
| 55 |
+
:param bias_shape: axes of bias added to output. Weights of this shape are created and stored. If `None` (the default), no bias is added.
|
| 56 |
+
:param axes_lengths: dimensions of weight tensor
|
| 57 |
+
"""
|
| 58 |
+
super().__init__()
|
| 59 |
+
self.pattern = pattern
|
| 60 |
+
self.weight_shape = weight_shape
|
| 61 |
+
self.bias_shape = bias_shape
|
| 62 |
+
self.axes_lengths = axes_lengths
|
| 63 |
+
self.initialize_einmix(
|
| 64 |
+
pattern=pattern, weight_shape=weight_shape, bias_shape=bias_shape, axes_lengths=axes_lengths
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def initialize_einmix(self, pattern: str, weight_shape: str, bias_shape: Optional[str], axes_lengths: dict):
|
| 68 |
+
left_pattern, right_pattern = pattern.split("->")
|
| 69 |
+
left = ParsedExpression(left_pattern)
|
| 70 |
+
right = ParsedExpression(right_pattern)
|
| 71 |
+
weight = ParsedExpression(weight_shape)
|
| 72 |
+
_report_axes(
|
| 73 |
+
set.difference(right.identifiers, {*left.identifiers, *weight.identifiers}),
|
| 74 |
+
"Unrecognized identifiers on the right side of EinMix {}",
|
| 75 |
+
)
|
| 76 |
+
if weight.has_ellipsis:
|
| 77 |
+
raise EinopsError("Ellipsis is not supported in weight, as its shape should be fully specified")
|
| 78 |
+
if left.has_ellipsis or right.has_ellipsis:
|
| 79 |
+
if not (left.has_ellipsis and right.has_ellipsis):
|
| 80 |
+
raise EinopsError(f"Ellipsis in EinMix should be on both sides, {pattern}")
|
| 81 |
+
if left.has_ellipsis_parenthesized:
|
| 82 |
+
raise EinopsError(f"Ellipsis on left side can't be in parenthesis, got {pattern}")
|
| 83 |
+
if any(x.has_non_unitary_anonymous_axes for x in [left, right, weight]):
|
| 84 |
+
raise EinopsError("Anonymous axes (numbers) are not allowed in EinMix")
|
| 85 |
+
if "(" in weight_shape or ")" in weight_shape:
|
| 86 |
+
raise EinopsError(f"Parenthesis is not allowed in weight shape: {weight_shape}")
|
| 87 |
+
|
| 88 |
+
pre_reshape_pattern = None
|
| 89 |
+
pre_reshape_lengths = None
|
| 90 |
+
post_reshape_pattern = None
|
| 91 |
+
if any(len(group) != 1 for group in left.composition):
|
| 92 |
+
names: List[str] = []
|
| 93 |
+
for group in left.composition:
|
| 94 |
+
names += group
|
| 95 |
+
names = [name if name != _ellipsis else "..." for name in names]
|
| 96 |
+
composition = " ".join(names)
|
| 97 |
+
pre_reshape_pattern = f"{left_pattern}-> {composition}"
|
| 98 |
+
pre_reshape_lengths = {name: length for name, length in axes_lengths.items() if name in names}
|
| 99 |
+
|
| 100 |
+
if any(len(group) != 1 for group in right.composition) or right.has_ellipsis_parenthesized:
|
| 101 |
+
names = []
|
| 102 |
+
for group in right.composition:
|
| 103 |
+
names += group
|
| 104 |
+
names = [name if name != _ellipsis else "..." for name in names]
|
| 105 |
+
composition = " ".join(names)
|
| 106 |
+
post_reshape_pattern = f"{composition} ->{right_pattern}"
|
| 107 |
+
|
| 108 |
+
self._create_rearrange_layers(pre_reshape_pattern, pre_reshape_lengths, post_reshape_pattern, {})
|
| 109 |
+
|
| 110 |
+
for axis in weight.identifiers:
|
| 111 |
+
if axis not in axes_lengths:
|
| 112 |
+
raise EinopsError("Dimension {} of weight should be specified".format(axis))
|
| 113 |
+
_report_axes(
|
| 114 |
+
set.difference(set(axes_lengths), {*left.identifiers, *weight.identifiers}),
|
| 115 |
+
"Axes {} are not used in pattern",
|
| 116 |
+
)
|
| 117 |
+
_report_axes(
|
| 118 |
+
set.difference(weight.identifiers, {*left.identifiers, *right.identifiers}), "Weight axes {} are redundant"
|
| 119 |
+
)
|
| 120 |
+
if len(weight.identifiers) == 0:
|
| 121 |
+
warnings.warn("EinMix: weight has no dimensions (means multiplication by a number)")
|
| 122 |
+
|
| 123 |
+
_weight_shape = [axes_lengths[axis] for (axis,) in weight.composition]
|
| 124 |
+
# single output element is a combination of fan_in input elements
|
| 125 |
+
_fan_in = _product([axes_lengths[axis] for (axis,) in weight.composition if axis not in right.identifiers])
|
| 126 |
+
if bias_shape is not None:
|
| 127 |
+
# maybe I should put ellipsis in the beginning for simplicity?
|
| 128 |
+
if not isinstance(bias_shape, str):
|
| 129 |
+
raise EinopsError("bias shape should be string specifying which axes bias depends on")
|
| 130 |
+
bias = ParsedExpression(bias_shape)
|
| 131 |
+
_report_axes(
|
| 132 |
+
set.difference(bias.identifiers, right.identifiers),
|
| 133 |
+
"Bias axes {} not present in output",
|
| 134 |
+
)
|
| 135 |
+
_report_axes(
|
| 136 |
+
set.difference(bias.identifiers, set(axes_lengths)),
|
| 137 |
+
"Sizes not provided for bias axes {}",
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
_bias_shape = []
|
| 141 |
+
used_non_trivial_size = False
|
| 142 |
+
for axes in right.composition:
|
| 143 |
+
if axes == _ellipsis:
|
| 144 |
+
if used_non_trivial_size:
|
| 145 |
+
raise EinopsError("all bias dimensions should go after ellipsis in the output")
|
| 146 |
+
else:
|
| 147 |
+
# handles ellipsis correctly
|
| 148 |
+
for axis in axes:
|
| 149 |
+
if axis == _ellipsis:
|
| 150 |
+
if used_non_trivial_size:
|
| 151 |
+
raise EinopsError("all bias dimensions should go after ellipsis in the output")
|
| 152 |
+
elif axis in bias.identifiers:
|
| 153 |
+
_bias_shape.append(axes_lengths[axis])
|
| 154 |
+
used_non_trivial_size = True
|
| 155 |
+
else:
|
| 156 |
+
_bias_shape.append(1)
|
| 157 |
+
else:
|
| 158 |
+
_bias_shape = None
|
| 159 |
+
|
| 160 |
+
weight_bound = (3 / _fan_in) ** 0.5
|
| 161 |
+
bias_bound = (1 / _fan_in) ** 0.5
|
| 162 |
+
self._create_parameters(_weight_shape, weight_bound, _bias_shape, bias_bound)
|
| 163 |
+
|
| 164 |
+
# rewrite einsum expression with single-letter latin identifiers so that
|
| 165 |
+
# expression will be understood by any framework
|
| 166 |
+
mapped_identifiers = {*left.identifiers, *right.identifiers, *weight.identifiers}
|
| 167 |
+
if _ellipsis in mapped_identifiers:
|
| 168 |
+
mapped_identifiers.remove(_ellipsis)
|
| 169 |
+
mapped_identifiers = list(sorted(mapped_identifiers))
|
| 170 |
+
mapping2letters = {k: letter for letter, k in zip(string.ascii_lowercase, mapped_identifiers)}
|
| 171 |
+
mapping2letters[_ellipsis] = "..." # preserve ellipsis
|
| 172 |
+
|
| 173 |
+
def write_flat_remapped(axes: ParsedExpression):
|
| 174 |
+
result = []
|
| 175 |
+
for composed_axis in axes.composition:
|
| 176 |
+
if isinstance(composed_axis, list):
|
| 177 |
+
result.extend([mapping2letters[axis] for axis in composed_axis])
|
| 178 |
+
else:
|
| 179 |
+
assert composed_axis == _ellipsis
|
| 180 |
+
result.append("...")
|
| 181 |
+
return "".join(result)
|
| 182 |
+
|
| 183 |
+
self.einsum_pattern: str = "{},{}->{}".format(
|
| 184 |
+
write_flat_remapped(left),
|
| 185 |
+
write_flat_remapped(weight),
|
| 186 |
+
write_flat_remapped(right),
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
def _create_rearrange_layers(
|
| 190 |
+
self,
|
| 191 |
+
pre_reshape_pattern: Optional[str],
|
| 192 |
+
pre_reshape_lengths: Optional[Dict],
|
| 193 |
+
post_reshape_pattern: Optional[str],
|
| 194 |
+
post_reshape_lengths: Optional[Dict],
|
| 195 |
+
):
|
| 196 |
+
raise NotImplementedError("Should be defined in framework implementations")
|
| 197 |
+
|
| 198 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 199 |
+
"""Shape and implementations"""
|
| 200 |
+
raise NotImplementedError("Should be defined in framework implementations")
|
| 201 |
+
|
| 202 |
+
def __repr__(self):
|
| 203 |
+
params = repr(self.pattern)
|
| 204 |
+
params += f", '{self.weight_shape}'"
|
| 205 |
+
if self.bias_shape is not None:
|
| 206 |
+
params += f", '{self.bias_shape}'"
|
| 207 |
+
for axis, length in self.axes_lengths.items():
|
| 208 |
+
params += ", {}={}".format(axis, length)
|
| 209 |
+
return "{}({})".format(self.__class__.__name__, params)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class _EinmixDebugger(_EinmixMixin):
|
| 213 |
+
"""Used only to test mixin"""
|
| 214 |
+
|
| 215 |
+
def _create_rearrange_layers(
|
| 216 |
+
self,
|
| 217 |
+
pre_reshape_pattern: Optional[str],
|
| 218 |
+
pre_reshape_lengths: Optional[Dict],
|
| 219 |
+
post_reshape_pattern: Optional[str],
|
| 220 |
+
post_reshape_lengths: Optional[Dict],
|
| 221 |
+
):
|
| 222 |
+
self.pre_reshape_pattern = pre_reshape_pattern
|
| 223 |
+
self.pre_reshape_lengths = pre_reshape_lengths
|
| 224 |
+
self.post_reshape_pattern = post_reshape_pattern
|
| 225 |
+
self.post_reshape_lengths = post_reshape_lengths
|
| 226 |
+
|
| 227 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 228 |
+
self.saved_weight_shape = weight_shape
|
| 229 |
+
self.saved_bias_shape = bias_shape
|
build/torch-cuda/layers/flax.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import field
|
| 2 |
+
from typing import Optional, Dict, cast
|
| 3 |
+
|
| 4 |
+
import flax.linen as nn
|
| 5 |
+
import jax
|
| 6 |
+
import jax.numpy as jnp
|
| 7 |
+
|
| 8 |
+
from . import RearrangeMixin, ReduceMixin
|
| 9 |
+
from ._einmix import _EinmixMixin
|
| 10 |
+
|
| 11 |
+
__author__ = "Alex Rogozhnikov"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Reduce(nn.Module):
|
| 15 |
+
pattern: str
|
| 16 |
+
reduction: str
|
| 17 |
+
sizes: dict = field(default_factory=lambda: {})
|
| 18 |
+
|
| 19 |
+
def setup(self):
|
| 20 |
+
self.reducer = ReduceMixin(self.pattern, self.reduction, **self.sizes)
|
| 21 |
+
|
| 22 |
+
def __call__(self, input):
|
| 23 |
+
return self.reducer._apply_recipe(input)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Rearrange(nn.Module):
|
| 27 |
+
pattern: str
|
| 28 |
+
sizes: dict = field(default_factory=lambda: {})
|
| 29 |
+
|
| 30 |
+
def setup(self):
|
| 31 |
+
self.rearranger = RearrangeMixin(self.pattern, **self.sizes)
|
| 32 |
+
|
| 33 |
+
def __call__(self, input):
|
| 34 |
+
return self.rearranger._apply_recipe(input)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class EinMix(nn.Module, _EinmixMixin):
|
| 38 |
+
pattern: str
|
| 39 |
+
weight_shape: str
|
| 40 |
+
bias_shape: Optional[str] = None
|
| 41 |
+
sizes: dict = field(default_factory=lambda: {})
|
| 42 |
+
|
| 43 |
+
def setup(self):
|
| 44 |
+
self.initialize_einmix(
|
| 45 |
+
pattern=self.pattern,
|
| 46 |
+
weight_shape=self.weight_shape,
|
| 47 |
+
bias_shape=self.bias_shape,
|
| 48 |
+
axes_lengths=self.sizes,
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 52 |
+
self.weight = self.param("weight", jax.nn.initializers.uniform(weight_bound), weight_shape)
|
| 53 |
+
|
| 54 |
+
if bias_shape is not None:
|
| 55 |
+
self.bias = self.param("bias", jax.nn.initializers.uniform(bias_bound), bias_shape)
|
| 56 |
+
else:
|
| 57 |
+
self.bias = None
|
| 58 |
+
|
| 59 |
+
def _create_rearrange_layers(
|
| 60 |
+
self,
|
| 61 |
+
pre_reshape_pattern: Optional[str],
|
| 62 |
+
pre_reshape_lengths: Optional[Dict],
|
| 63 |
+
post_reshape_pattern: Optional[str],
|
| 64 |
+
post_reshape_lengths: Optional[Dict],
|
| 65 |
+
):
|
| 66 |
+
self.pre_rearrange = None
|
| 67 |
+
if pre_reshape_pattern is not None:
|
| 68 |
+
self.pre_rearrange = Rearrange(pre_reshape_pattern, sizes=cast(dict, pre_reshape_lengths))
|
| 69 |
+
|
| 70 |
+
self.post_rearrange = None
|
| 71 |
+
if post_reshape_pattern is not None:
|
| 72 |
+
self.post_rearrange = Rearrange(post_reshape_pattern, sizes=cast(dict, post_reshape_lengths))
|
| 73 |
+
|
| 74 |
+
def __call__(self, input):
|
| 75 |
+
if self.pre_rearrange is not None:
|
| 76 |
+
input = self.pre_rearrange(input)
|
| 77 |
+
result = jnp.einsum(self.einsum_pattern, input, self.weight)
|
| 78 |
+
if self.bias is not None:
|
| 79 |
+
result += self.bias
|
| 80 |
+
if self.post_rearrange is not None:
|
| 81 |
+
result = self.post_rearrange(result)
|
| 82 |
+
return result
|
build/torch-cuda/layers/keras.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__author__ = "Alex Rogozhnikov"
|
| 2 |
+
|
| 3 |
+
from ..layers.tensorflow import Rearrange, Reduce, EinMix
|
| 4 |
+
|
| 5 |
+
keras_custom_objects = {
|
| 6 |
+
Rearrange.__name__: Rearrange,
|
| 7 |
+
Reduce.__name__: Reduce,
|
| 8 |
+
EinMix.__name__: EinMix,
|
| 9 |
+
}
|
build/torch-cuda/layers/oneflow.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Dict, cast
|
| 2 |
+
|
| 3 |
+
import oneflow as flow
|
| 4 |
+
|
| 5 |
+
from . import RearrangeMixin, ReduceMixin
|
| 6 |
+
from ._einmix import _EinmixMixin
|
| 7 |
+
|
| 8 |
+
__author__ = "Tianhe Ren & Depeng Liang"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Rearrange(RearrangeMixin, flow.nn.Module):
|
| 12 |
+
def forward(self, input):
|
| 13 |
+
return self._apply_recipe(input)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Reduce(ReduceMixin, flow.nn.Module):
|
| 17 |
+
def forward(self, input):
|
| 18 |
+
return self._apply_recipe(input)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class EinMix(_EinmixMixin, flow.nn.Module):
|
| 22 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 23 |
+
self.weight = flow.nn.Parameter(
|
| 24 |
+
flow.zeros(weight_shape).uniform_(-weight_bound, weight_bound), requires_grad=True
|
| 25 |
+
)
|
| 26 |
+
if bias_shape is not None:
|
| 27 |
+
self.bias = flow.nn.Parameter(flow.zeros(bias_shape).uniform_(-bias_bound, bias_bound), requires_grad=True)
|
| 28 |
+
else:
|
| 29 |
+
self.bias = None
|
| 30 |
+
|
| 31 |
+
def _create_rearrange_layers(
|
| 32 |
+
self,
|
| 33 |
+
pre_reshape_pattern: Optional[str],
|
| 34 |
+
pre_reshape_lengths: Optional[Dict],
|
| 35 |
+
post_reshape_pattern: Optional[str],
|
| 36 |
+
post_reshape_lengths: Optional[Dict],
|
| 37 |
+
):
|
| 38 |
+
self.pre_rearrange = None
|
| 39 |
+
if pre_reshape_pattern is not None:
|
| 40 |
+
self.pre_rearrange = Rearrange(pre_reshape_pattern, **cast(dict, pre_reshape_lengths))
|
| 41 |
+
|
| 42 |
+
self.post_rearrange = None
|
| 43 |
+
if post_reshape_pattern is not None:
|
| 44 |
+
self.post_rearrange = Rearrange(post_reshape_pattern, **cast(dict, post_reshape_lengths))
|
| 45 |
+
|
| 46 |
+
def forward(self, input):
|
| 47 |
+
if self.pre_rearrange is not None:
|
| 48 |
+
input = self.pre_rearrange(input)
|
| 49 |
+
result = flow.einsum(self.einsum_pattern, input, self.weight)
|
| 50 |
+
if self.bias is not None:
|
| 51 |
+
result += self.bias
|
| 52 |
+
if self.post_rearrange is not None:
|
| 53 |
+
result = self.post_rearrange(result)
|
| 54 |
+
return result
|
build/torch-cuda/layers/paddle.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Dict, cast
|
| 2 |
+
|
| 3 |
+
import paddle
|
| 4 |
+
|
| 5 |
+
from . import RearrangeMixin, ReduceMixin
|
| 6 |
+
from ._einmix import _EinmixMixin
|
| 7 |
+
|
| 8 |
+
__author__ = "PaddlePaddle"
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Rearrange(RearrangeMixin, paddle.nn.Layer):
|
| 12 |
+
def forward(self, input):
|
| 13 |
+
return self._apply_recipe(input)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Reduce(ReduceMixin, paddle.nn.Layer):
|
| 17 |
+
def forward(self, input):
|
| 18 |
+
return self._apply_recipe(input)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class EinMix(_EinmixMixin, paddle.nn.Layer):
|
| 22 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 23 |
+
self.weight = self.create_parameter(
|
| 24 |
+
weight_shape, default_initializer=paddle.nn.initializer.Uniform(-weight_bound, weight_bound)
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
if bias_shape is not None:
|
| 28 |
+
self.bias = self.create_parameter(
|
| 29 |
+
bias_shape, default_initializer=paddle.nn.initializer.Uniform(-bias_bound, bias_bound)
|
| 30 |
+
)
|
| 31 |
+
else:
|
| 32 |
+
self.bias = None
|
| 33 |
+
|
| 34 |
+
def _create_rearrange_layers(
|
| 35 |
+
self,
|
| 36 |
+
pre_reshape_pattern: Optional[str],
|
| 37 |
+
pre_reshape_lengths: Optional[Dict],
|
| 38 |
+
post_reshape_pattern: Optional[str],
|
| 39 |
+
post_reshape_lengths: Optional[Dict],
|
| 40 |
+
):
|
| 41 |
+
self.pre_rearrange = None
|
| 42 |
+
if pre_reshape_pattern is not None:
|
| 43 |
+
self.pre_rearrange = Rearrange(pre_reshape_pattern, **cast(dict, pre_reshape_lengths))
|
| 44 |
+
|
| 45 |
+
self.post_rearrange = None
|
| 46 |
+
if post_reshape_pattern is not None:
|
| 47 |
+
self.post_rearrange = Rearrange(post_reshape_pattern, **cast(dict, post_reshape_lengths))
|
| 48 |
+
|
| 49 |
+
def forward(self, input):
|
| 50 |
+
if self.pre_rearrange is not None:
|
| 51 |
+
input = self.pre_rearrange(input)
|
| 52 |
+
|
| 53 |
+
result = paddle.einsum(self.einsum_pattern, input, self.weight)
|
| 54 |
+
if self.bias is not None:
|
| 55 |
+
result += self.bias
|
| 56 |
+
if self.post_rearrange is not None:
|
| 57 |
+
result = self.post_rearrange(result)
|
| 58 |
+
return result
|
build/torch-cuda/layers/tensorflow.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Comment about tensorflow layers:
|
| 3 |
+
unfortunately instructions on creation of TF layers change constantly,
|
| 4 |
+
and changed way too many times at this point to remember what-compatible-where.
|
| 5 |
+
|
| 6 |
+
Layers in einops==0.7.0 (and several prior versions)
|
| 7 |
+
are compatible with TF 2.13
|
| 8 |
+
|
| 9 |
+
Layers in einops==0.8.0 were re-implemented
|
| 10 |
+
according to official instructions for TF 2.16
|
| 11 |
+
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from typing import Optional, Dict, cast
|
| 15 |
+
|
| 16 |
+
import tensorflow as tf
|
| 17 |
+
from tensorflow.keras.layers import Layer
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
from . import RearrangeMixin, ReduceMixin
|
| 21 |
+
from ._einmix import _EinmixMixin
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
__author__ = "Alex Rogozhnikov"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Rearrange(RearrangeMixin, Layer):
|
| 28 |
+
def build(self, input_shape):
|
| 29 |
+
pass # layer does not have any parameters to be initialized
|
| 30 |
+
|
| 31 |
+
def call(self, inputs):
|
| 32 |
+
return self._apply_recipe(inputs)
|
| 33 |
+
|
| 34 |
+
def get_config(self):
|
| 35 |
+
return {"pattern": self.pattern, **self.axes_lengths}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class Reduce(ReduceMixin, Layer):
|
| 39 |
+
def build(self, input_shape):
|
| 40 |
+
pass # layer does not have any parameters to be initialized
|
| 41 |
+
|
| 42 |
+
def call(self, inputs):
|
| 43 |
+
return self._apply_recipe(inputs)
|
| 44 |
+
|
| 45 |
+
def get_config(self):
|
| 46 |
+
return {"pattern": self.pattern, "reduction": self.reduction, **self.axes_lengths}
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class EinMix(_EinmixMixin, Layer):
|
| 50 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 51 |
+
# this method is called in __init__,
|
| 52 |
+
# but we postpone actual creation to build(), as TF instruction suggests
|
| 53 |
+
self._params = [weight_shape, weight_bound, bias_shape, bias_bound]
|
| 54 |
+
|
| 55 |
+
def _create_rearrange_layers(
|
| 56 |
+
self,
|
| 57 |
+
pre_reshape_pattern: Optional[str],
|
| 58 |
+
pre_reshape_lengths: Optional[Dict],
|
| 59 |
+
post_reshape_pattern: Optional[str],
|
| 60 |
+
post_reshape_lengths: Optional[Dict],
|
| 61 |
+
):
|
| 62 |
+
self.pre_rearrange = None
|
| 63 |
+
if pre_reshape_pattern is not None:
|
| 64 |
+
self.pre_rearrange = Rearrange(pre_reshape_pattern, **cast(dict, pre_reshape_lengths))
|
| 65 |
+
|
| 66 |
+
self.post_rearrange = None
|
| 67 |
+
if post_reshape_pattern is not None:
|
| 68 |
+
self.post_rearrange = Rearrange(post_reshape_pattern, **cast(dict, post_reshape_lengths))
|
| 69 |
+
|
| 70 |
+
def build(self, input_shape):
|
| 71 |
+
[weight_shape, weight_bound, bias_shape, bias_bound] = self._params
|
| 72 |
+
self.weight = self.add_weight(
|
| 73 |
+
shape=weight_shape,
|
| 74 |
+
initializer=tf.random_uniform_initializer(-weight_bound, weight_bound),
|
| 75 |
+
trainable=True,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
if bias_shape is not None:
|
| 79 |
+
self.bias = self.add_weight(
|
| 80 |
+
shape=bias_shape,
|
| 81 |
+
initializer=tf.random_uniform_initializer(-bias_bound, bias_bound),
|
| 82 |
+
trainable=True,
|
| 83 |
+
)
|
| 84 |
+
else:
|
| 85 |
+
self.bias = None
|
| 86 |
+
|
| 87 |
+
def call(self, inputs):
|
| 88 |
+
if self.pre_rearrange is not None:
|
| 89 |
+
inputs = self.pre_rearrange(inputs)
|
| 90 |
+
result = tf.einsum(self.einsum_pattern, inputs, self.weight)
|
| 91 |
+
if self.bias is not None:
|
| 92 |
+
result = result + self.bias
|
| 93 |
+
if self.post_rearrange is not None:
|
| 94 |
+
result = self.post_rearrange(result)
|
| 95 |
+
return result
|
| 96 |
+
|
| 97 |
+
def get_config(self):
|
| 98 |
+
return {
|
| 99 |
+
"pattern": self.pattern,
|
| 100 |
+
"weight_shape": self.weight_shape,
|
| 101 |
+
"bias_shape": self.bias_shape,
|
| 102 |
+
**self.axes_lengths,
|
| 103 |
+
}
|
build/torch-cuda/layers/torch.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional, Dict, cast
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from . import RearrangeMixin, ReduceMixin
|
| 6 |
+
from ._einmix import _EinmixMixin
|
| 7 |
+
from .._torch_specific import apply_for_scriptable_torch
|
| 8 |
+
|
| 9 |
+
__author__ = "Alex Rogozhnikov"
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Rearrange(RearrangeMixin, torch.nn.Module):
|
| 13 |
+
def forward(self, input):
|
| 14 |
+
recipe = self._multirecipe[input.ndim]
|
| 15 |
+
return apply_for_scriptable_torch(recipe, input, reduction_type="rearrange", axes_dims=self._axes_lengths)
|
| 16 |
+
|
| 17 |
+
def _apply_recipe(self, x):
|
| 18 |
+
# overriding parent method to prevent it's scripting
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Reduce(ReduceMixin, torch.nn.Module):
|
| 23 |
+
def forward(self, input):
|
| 24 |
+
recipe = self._multirecipe[input.ndim]
|
| 25 |
+
return apply_for_scriptable_torch(recipe, input, reduction_type=self.reduction, axes_dims=self._axes_lengths)
|
| 26 |
+
|
| 27 |
+
def _apply_recipe(self, x):
|
| 28 |
+
# overriding parent method to prevent it's scripting
|
| 29 |
+
pass
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class EinMix(_EinmixMixin, torch.nn.Module):
|
| 33 |
+
def _create_parameters(self, weight_shape, weight_bound, bias_shape, bias_bound):
|
| 34 |
+
self.weight = torch.nn.Parameter(
|
| 35 |
+
torch.zeros(weight_shape).uniform_(-weight_bound, weight_bound), requires_grad=True
|
| 36 |
+
)
|
| 37 |
+
if bias_shape is not None:
|
| 38 |
+
self.bias = torch.nn.Parameter(
|
| 39 |
+
torch.zeros(bias_shape).uniform_(-bias_bound, bias_bound), requires_grad=True
|
| 40 |
+
)
|
| 41 |
+
else:
|
| 42 |
+
self.bias = None
|
| 43 |
+
|
| 44 |
+
def _create_rearrange_layers(
|
| 45 |
+
self,
|
| 46 |
+
pre_reshape_pattern: Optional[str],
|
| 47 |
+
pre_reshape_lengths: Optional[Dict],
|
| 48 |
+
post_reshape_pattern: Optional[str],
|
| 49 |
+
post_reshape_lengths: Optional[Dict],
|
| 50 |
+
):
|
| 51 |
+
self.pre_rearrange = None
|
| 52 |
+
if pre_reshape_pattern is not None:
|
| 53 |
+
self.pre_rearrange = Rearrange(pre_reshape_pattern, **cast(dict, pre_reshape_lengths))
|
| 54 |
+
|
| 55 |
+
self.post_rearrange = None
|
| 56 |
+
if post_reshape_pattern is not None:
|
| 57 |
+
self.post_rearrange = Rearrange(post_reshape_pattern, **cast(dict, post_reshape_lengths))
|
| 58 |
+
|
| 59 |
+
def forward(self, input):
|
| 60 |
+
if self.pre_rearrange is not None:
|
| 61 |
+
input = self.pre_rearrange(input)
|
| 62 |
+
result = torch.einsum(self.einsum_pattern, input, self.weight)
|
| 63 |
+
if self.bias is not None:
|
| 64 |
+
result += self.bias
|
| 65 |
+
if self.post_rearrange is not None:
|
| 66 |
+
result = self.post_rearrange(result)
|
| 67 |
+
return result
|
build/torch-cuda/metadata.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "einops",
|
| 3 |
+
"id": "_einops_cuda_d45adda",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"upstream": "https://github.com/arogozhnikov/einops.git",
|
| 7 |
+
"python-depends": [],
|
| 8 |
+
"backend": {
|
| 9 |
+
"type": "cuda"
|
| 10 |
+
},
|
| 11 |
+
"digest": {
|
| 12 |
+
"algorithm": "sha256",
|
| 13 |
+
"files": {
|
| 14 |
+
"__init__.py": "Zi5UF5cdv0EjT90oh0zsv0Jfi4QbTrfZnE0Tv5on8F8=",
|
| 15 |
+
"_backends.py": "7BAiKWdBLLvAiryfhwDbwxglGDW9Dh5FNMT1ERSuzxk=",
|
| 16 |
+
"_ops.py": "11R2mMPUK6wLQIwJo8geCYiUgC/WM4OCjMiT15zK3hQ=",
|
| 17 |
+
"_torch_specific.py": "CxpGcgqlBn4BH+Qkpmr3dFBPTid1Rz/R0f9TGb36Bsw=",
|
| 18 |
+
"array_api.py": "jOb8RhwLS9wob/Y/e/KrnBR6ihQPoB2Ly0tfrHr+/Zk=",
|
| 19 |
+
"einops.py": "sXvD8SWFqufziyQJKRPmfAGHVN1cMDvYOPNuZ8L1XQU=",
|
| 20 |
+
"experimental/__init__.py": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
| 21 |
+
"experimental/indexing.py": "yFFflW3+kV6/5PPJU7/jOJsJBCWCWlE4dGlu9gwSPXo=",
|
| 22 |
+
"layers/__init__.py": "vBtnAt2afs4QlqpeFU4dlZNxBuC9IXl3fmilk+2OzHM=",
|
| 23 |
+
"layers/_einmix.py": "9cDMcCmn2y1jN9xLx587GAY1GCb+9TvEjJWaNNX4Vps=",
|
| 24 |
+
"layers/flax.py": "zFy83gSLRm31cLuKFRvZ82/HsefnXPbRvkKZh1KkC1I=",
|
| 25 |
+
"layers/keras.py": "+7So0w94phvf9HdW0xi2mSeBg02qVPvAyfp/1XR02NM=",
|
| 26 |
+
"layers/oneflow.py": "YEPzz4xc7BDRQfb8ulD3teqQJdbO6qQg7Z4KIPVTLz8=",
|
| 27 |
+
"layers/paddle.py": "8cRZQ8BT9vYEczh7pNProuTM/3XjLty2ht2sdvXNFiI=",
|
| 28 |
+
"layers/tensorflow.py": "T9uhSVwbXREahc31ARAHoN5K+7zsuS8NRNPdY6Zk1Bc=",
|
| 29 |
+
"layers/torch.py": "504G99kEgy7dk1UPBbj9hzJmZkAHwVhMDFN/8J+p3C8=",
|
| 30 |
+
"packing.py": "vBjwbVWs3OwmI83BMNoeu3jBFAbAiy10i3ClmZxZtCQ=",
|
| 31 |
+
"parsing.py": "tXcSr4W1mbePUu+oIYgG0cKYNxsPXuVYFIHynj1FCg4="
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
"provenance": {
|
| 35 |
+
"kernel-builder": {
|
| 36 |
+
"version": "0.17.0-dev0",
|
| 37 |
+
"sha": "d0610aa58db33b142c86b59598a2a1c730f52996",
|
| 38 |
+
"dirty": false
|
| 39 |
+
},
|
| 40 |
+
"kernel": {
|
| 41 |
+
"sha": "d45addadb0c380f3d7cc2813310b3bcd75f8aa9a",
|
| 42 |
+
"dirty": false
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
build/torch-cuda/metadata.json.sigstore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHSzCCBtGgAwIBAgIUUacGOOXSbwomtiDehEauSQXdJwwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwODA3MTM0NDU2WhcNMjYwODA3MTM1NDU2WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWdnIdX2ky5B75yPQrw7yVzWMLKh4/+hxIVc7Xht7bvW9sWDfTFvAk9SI6ouqivPAi6lpo15e3xwvy8+vj2V5TqOCBfAwggXsMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUJ67AbLESuta0gOytiK2nhg62RT8wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wawYDVR0RAQH/BGEwX4ZdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDkGCisGAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGDvzABAwQoZDQ1YWRkYWRiMGMzODBmM2Q3Y2MyODEzMzEwYjNiY2Q3NWY4YWE5YTATBgorBgEEAYO/MAEEBAVCdWlsZDArBgorBgEEAYO/MAEFBB1odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTAdBgorBgEEAYO/MAEGBA9yZWZzL2hlYWRzL21haW4wOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMG0GCisGAQQBg78wAQkEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wAQoEKgwoZDQ1YWRkYWRiMGMzODBmM2Q3Y2MyODEzMzEwYjNiY2Q3NWY4YWE5YTAbBgorBgEEAYO/MAELBA0MC3NlbGYtaG9zdGVkMEAGCisGAQQBg78wAQwEMgwwaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MDgGCisGAQQBg78wAQ0EKgwoZDQ1YWRkYWRiMGMzODBmM2Q3Y2MyODEzMzEwYjNiY2Q3NWY4YWE5YTAfBgorBgEEAYO/MAEOBBEMD3JlZnMvaGVhZHMvbWFpbjAaBgorBgEEAYO/MAEPBAwMCjEwNzE0NzU1MjkwLgYKKwYBBAGDvzABEAQgDB5odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2UwGAYKKwYBBAGDvzABEQQKDAgyNTcyMDc0MzBtBgorBgEEAYO/MAESBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS8uZ2l0aHViL3dvcmtmbG93cy9idWlsZC55YW1sQHJlZnMvaGVhZHMvbWFpbjA4BgorBgEEAYO/MAETBCoMKGQ0NWFkZGFkYjBjMzgwZjNkN2NjMjgxMzMxMGIzYmNkNzVmOGFhOWEwIQYKKwYBBAGDvzABFAQTDBF3b3JrZmxvd19kaXNwYXRjaDBkBgorBgEEAYO/MAEVBFYMVGh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eS9hY3Rpb25zL3J1bnMvMzExODM4OTEwNDkvYXR0ZW1wdHMvMTAWBgorBgEEAYO/MAEWBAgMBnB1YmxpYzBGBgorBgEEAYO/MAEYBDgMNnJlcG86aHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHk6cmVmOnJlZnMvaGVhZHMvbWFpbjCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABn9x4QXQAAAQDAEcwRQIhAN0G29OPXX93oRMXIydM/ZUDE1YY72DiGL7z+fNTV5LYAiAJeGPwHqGWVUGQdxIGkyHyALGPxESddGNwL4Lme8znYDAKBggqhkjOPQQDAwNoADBlAjBn62jhPKIHDtuErG2gU18qy5Ed+gyQ3E49hlgbvzWqw6LCpsdJujoG4o8Zm2PgJPkCMQDBbeoUjvA8MefZzoRWJYKlrdlc5cV3NEfxbPMW9KZHlXn4phUuGUOTvxdrgsq2YI4="},"tlogEntries":[{"logIndex":"2370478831","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1786110296","inclusionPromise":{"signedEntryTimestamp":"MEQCIA4ddvAnNNm4iLQdOUrp/YlF62yKHmcfUKouhmMp+53RAiB24MzS+UaRunfNspUoMSiKLE3CO+xPpNu7DVvA8XdROg=="},"inclusionProof":{"logIndex":"2248574569","rootHash":"xhF+Sk3KdHoNMadzxW/BHVnlSMPtvtFvzzedzMX7myo=","treeSize":"2248574572","hashes":["X2Iv4avwV+o67VMPuEKTbGrbWc2RxKOtpINPOr9TmaE=","Lc1oHGdjLyQYnP+8PeejD9qE8oi88gyyPzGwnwpZOGc=","jmc6FKLo0hnfILv8q3LOe6XkwB47NMfXrf+eE+NiC/s=","lG0vBfmy3NyKqsDg3rHUTYmwdKoqekKk/5yxaoIwaeE=","5IYm0izDiwos8kF2OODaxXQVp5GSAjhY7ysAOvm2xMs=","XXWXzYRrsBDaQeQZeD9JT3OtOuvg//LUnu+u3IwZFOQ=","1iszQpDQvYGSRDukIT2HFergOhdefiQIlIp9VIaKtco=","ByRm8lkUhtWxweoZD8P90jNd2or8eFiw04pJXBx1cHE=","BS5WEjUIQqZG8MjRFGLBhqD4ZyOzwjsu+LZ0qVkyKtU=","B+BvP8FCZx7ZE5eJJXnytVRt2HDakcIeugCRQ/HvxH8=","ILMoQSbIb83ZRi3LtJ9th99rqP+Za3UA534sa6mEYKg=","i5Zl8FZrDwxCDv2e2DNO2M8JvpR/c11ElvCZS53/teA=","xH/DCseLHr9eKoYT8qsORZK7zVdEGYWHuVtsVrD95wY="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n2248574572\nxhF+Sk3KdHoNMadzxW/BHVnlSMPtvtFvzzedzMX7myo=\n\n— rekor.sigstore.dev wNI9ajBEAiBpZrbe/RIeUbWTuvS/ILM3yQ0DnjkfWhoVTpN9Wl7PzAIgTN+mqrnFaae6S6qkei8QpiYq/ftmleO9NU5qjYDjojs=\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI2NDI3MzcwMmZmMDMwN2MwYjA2Y2E2MWMzOWE4ZjQ4YjE5OGFmM2RmNGE2ZGZiNjcwZGJiMDhkZjA4NjEwNjhkIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FWUNJUUNEWUx0ZGhWWDNpMEdQeHNPMXM3VmlJNjFEbUM0cmNORFBoYnpNZXpqbnNBSWhBT0VJVWZJd3dGY1VkcytmM1N1R0UyYUZ1MTY3cGFXR3I5V2dreTg3Y2g2ayIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaFRla05EUW5SSFowRjNTVUpCWjBsVlZXRmpSMDlQV0ZOaWQyOXRkR2xFWldoRllYVlRVVmhrU25kM2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFpkMDlFUVROTlZFMHdUa1JWTWxkb1kwNU5hbGwzVDBSQk0wMVVUVEZPUkZVeVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZYWkc1SlpGZ3lhM2sxUWpjMWVWQlJjbmMzZVZaNlYwMU1TMmcwTHl0b2VFbFdZemNLV0doME4ySjJWemx6VjBSbVZFWjJRV3M1VTBrMmIzVnhhWFpRUVdrMmJIQnZNVFZsTTNoM2RuazRLM1pxTWxZMVZIRlBRMEptUVhkbloxaHpUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZLTmpkQkNtSk1SVk4xZEdFd1owOTVkR2xMTW01b1p6WXlVbFE0ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJGM1dVUldVakJTUVZGSUwwSkhSWGRZTkZwa1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVERKb01Wb3laSEJpYldSdFdWZE9iQXBNTW5Sc1kyMDFiR0pJVFhSWk1qbDBZbGhXZFdGWVVqVk1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU1rb3hZVmQ0YTB4dWJHaGlWM2hCQ21OdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBkQlVWRkNaemM0ZDBGUlJVVkxNbWd3WkVoQ2VrOXBPSFprUnpseVdsYzBkVmxYVGpBS1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RXUkROV3BpTWpCM1NIZFpTMHQzV1VKQ1FVZEVkbnBCUWtGblVWSmtNamw1WVRKYWN3cGlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVkbnBCUWtGM1VXOWFSRkV4V1ZkU2ExbFhVbWxOUjAxNlQwUkNiVTB5VVROWk1rMTVDazlFUlhwTmVrVjNXV3BPYVZreVVUTk9WMWswV1ZkRk5WbFVRVlJDWjI5eVFtZEZSVUZaVHk5TlFVVkZRa0ZXUTJSWGJITmFSRUZ5UW1kdmNrSm5SVVVLUVZsUEwwMUJSVVpDUWpGdlpGZGtibUZYTlc1YWJVWnFXbE01Y2xwWVNuVmFWM2g2VEZkT2RtSlhNVEZpYld3d1pWUkJaRUpuYjNKQ1owVkZRVmxQTHdwTlFVVkhRa0U1ZVZwWFducE1NbWhzV1ZkU2Vrd3lNV2hoVnpSM1QzZFpTMHQzV1VKQ1FVZEVkbnBCUWtOQlVYUkVRM1J2WkVoU2QyTjZiM1pNTTFKMkNtRXlWblZNYlVacVpFZHNkbUp1VFhWYU1td3dZVWhXYVdSWVRteGpiVTUyWW01U2JHSnVVWFZaTWpsMFRVY3dSME5wYzBkQlVWRkNaemM0ZDBGUmEwVUtXSGQ0WkdGSVVqQmpTRTAyVEhrNWJtRllVbTlrVjBsMVdUSTVkRXd5YURGYU1tUndZbTFrYlZsWFRteE1NblJzWTIwMWJHSklUWFJaTWpsMFlsaFdkUXBoV0ZJMVRIazFibUZZVW05a1YwbDJaREk1ZVdFeVduTmlNMlI2VERKS01XRlhlR3RNYm14b1lsZDRRV050Vm0xamVUbHZXbGRHYTJONU9YUlpWMngxQ2sxRVowZERhWE5IUVZGUlFtYzNPSGRCVVc5RlMyZDNiMXBFVVRGWlYxSnJXVmRTYVUxSFRYcFBSRUp0VFRKUk0xa3lUWGxQUkVWNlRYcEZkMWxxVG1rS1dUSlJNMDVYV1RSWlYwVTFXVlJCWWtKbmIzSkNaMFZGUVZsUEwwMUJSVXhDUVRCTlF6Tk9iR0pIV1hSaFJ6bDZaRWRXYTAxRlFVZERhWE5IUVZGUlFncG5OemgzUVZGM1JVMW5kM2RoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlUxRVowZERhWE5IUVZGUlFtYzNPSGRCVVRCRlMyZDNiMXBFVVRGWlYxSnJXVmRTYVUxSFRYcFBSRUp0VFRKUk0xa3lUWGtLVDBSRmVrMTZSWGRaYWs1cFdUSlJNMDVYV1RSWlYwVTFXVlJCWmtKbmIzSkNaMFZGUVZsUEwwMUJSVTlDUWtWTlJETktiRnB1VFhaaFIxWm9Xa2hOZGdwaVYwWndZbXBCWVVKbmIzSkNaMFZGUVZsUEwwMUJSVkJDUVhkTlEycEZkMDU2UlRCT2VsVXhUV3ByZDB4bldVdExkMWxDUWtGSFJIWjZRVUpGUVZGbkNrUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWM1IwRlpTMHQzV1VKQ1FVZEVkbnBCUWtWUlVVc0tSRUZuZVU1VVkzbE5SR013VFhwQ2RFSm5iM0pDWjBWRlFWbFBMMDFCUlZOQ1JqaE5XRmRvTUdSSVFucFBhVGgyV2pKc01HRklWbWxNYlU1MllsTTVid3BrVjJSdVlWYzFibHB0Um1wYVV6bHlXbGhLZFZwWGVIcE1WMDUyWWxjeE1XSnRiREJsVXpoMVdqSnNNR0ZJVm1sTU0yUjJZMjEwYldKSE9UTmplVGxwQ21SWGJITmFRelUxV1ZjeGMxRklTbXhhYmsxMllVZFdhRnBJVFhaaVYwWndZbXBCTkVKbmIzSkNaMFZGUVZsUEwwMUJSVlJDUTI5TlMwZFJNRTVYUm1zS1drZEdhMWxxUW1wTmVtZDNXbXBPYTA0eVRtcE5hbWQ0VFhwTmVFMUhTWHBaYlU1clRucFdiVTlIUm1oUFYwVjNTVkZaUzB0M1dVSkNRVWRFZG5wQlFncEdRVkZVUkVKR00ySXpTbkphYlhoMlpERTVhMkZZVG5kWldGSnFZVVJDYTBKbmIzSkNaMFZGUVZsUEwwMUJSVlpDUmxsTlZrZG9NR1JJUW5wUGFUaDJDbG95YkRCaFNGWnBURzFPZG1KVE9XOWtWMlJ1WVZjMWJscHRSbXBhVXpseVdsaEtkVnBYZUhwTVYwNTJZbGN4TVdKdGJEQmxVemxvV1ROU2NHSXlOWG9LVEROS01XSnVUWFpOZWtWNFQwUk5ORTlVUlhkT1JHdDJXVmhTTUZwWE1YZGtTRTEyVFZSQlYwSm5iM0pDWjBWRlFWbFBMMDFCUlZkQ1FXZE5RbTVDTVFwWmJYaHdXWHBDUjBKbmIzSkNaMFZGUVZsUEwwMUJSVmxDUkdkTlRtNUtiR05IT0RaaFNGWnVXakpzZFZveVdtaFpNbFYyWVRKV2VXSnRWbk5qZVRGcUNtSXlNWFJrVnpWd1pFaHJObU50Vm0xUGJrcHNXbTVOZG1GSFZtaGFTRTEyWWxkR2NHSnFRMEpwWjFsTFMzZFpRa0pCU0ZkbFVVbEZRV2RTT0VKSWIwRUtaVUZDTWtGT01EbE5SM0pIZUhoRmVWbDRhMlZJU214dVRuZExhVk5zTmpRemFubDBMelJsUzJOdlFYWkxaVFpQUVVGQlFtNDVlRFJSV0ZGQlFVRlJSQXBCUldOM1VsRkphRUZPTUVjeU9VOVFXRmc1TTI5U1RWaEplV1JOTDFwVlJFVXhXVmszTWtScFIwdzNlaXRtVGxSV05VeFpRV2xCU21WSFVIZEljVWRYQ2xaVlIxRmtlRWxIYTNsSWVVRk1SMUI0UlZOa1pFZE9kMHcwVEcxbE9IcHVXVVJCUzBKblozRm9hMnBQVUZGUlJFRjNUbTlCUkVKc1FXcENiall5YW1nS1VFdEpTRVIwZFVWeVJ6Sm5WVEU0Y1hrMVJXUXJaM2xSTTBVME9XaHNaMkoyZWxkeGR6Wk1RM0J6WkVwMWFtOUhORzg0V20weVVHZEtVR3REVFZGRVFncGlaVzlWYW5aQk9FMWxabHA2YjFKWFNsbExiSEprYkdNMVkxWXpUa1ZtZUdKUVRWYzVTMXBJYkZodU5IQm9WWFZIVlU5VWRuaGtjbWR6Y1RKWlNUUTlDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifX19fQ=="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyDADAgEAMIICvwYJKoZIhvcNAQcCoIICsDCCAqwCAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgbkhBcHaQNxUEmWxJK4CpVHFAmtNtaRXoaCGBUaQcr1kCFE6NZqPwuGlzKx+1Ps+/X3Ay94XGGA8yMDI2MDgwNzEzNDQ1NlowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdowggHWAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYwODA3MTM0NDU2WjAvBgkqhkiG9w0BCQQxIgQg5Reu7QB5QfKf5T1fzU4O5t5c2c6k0fsObLh7Fx77v1MwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGYwZAIvEPL1+OVrRjfwSbXEUergXdZI/72Vc6lh07pIjIG1W/uvhCuzJGAinnFUjeJG8HkCMQCUcOD5vmAgXv19OrIy3ZDqrqq324j70th+nIkTZFyWJgma6R6PHJtvVdtQrSa0xME="}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"ZCc3Av8DB8CwbKYcOaj0ixmK899KbftnDbsI3whhBo0="},"signature":"MEYCIQCDYLtdhVX3i0GPxsO1s7ViI61DmC4rcNDPhbzMezjnsAIhAOEIUfIwwFcUds+f3SuGE2aFu167paWGr9Wgky87ch6k"}}
|
build/torch-cuda/packing.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import lru_cache
|
| 2 |
+
from typing import List, Union, TypeVar, Tuple, Sequence
|
| 3 |
+
|
| 4 |
+
from . import EinopsError
|
| 5 |
+
|
| 6 |
+
from ._backends import get_backend
|
| 7 |
+
from .parsing import ParsedExpression
|
| 8 |
+
|
| 9 |
+
Tensor = TypeVar("Tensor")
|
| 10 |
+
|
| 11 |
+
Shape = Union[Tuple[int, ...], List[int]]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@lru_cache(maxsize=128)
|
| 15 |
+
def analyze_pattern(pattern: str, opname: str) -> Tuple[int, int, int]:
|
| 16 |
+
# Maybe some validation of identifiers?
|
| 17 |
+
axes = pattern.split()
|
| 18 |
+
axes_set = set(axes)
|
| 19 |
+
if len(axes) != len(axes_set):
|
| 20 |
+
raise EinopsError(f'Duplicates in axes names in {opname}(..., "{pattern}")')
|
| 21 |
+
if "*" not in axes_set:
|
| 22 |
+
raise EinopsError(f'No *-axis in {opname}(..., "{pattern}")')
|
| 23 |
+
for axis in axes:
|
| 24 |
+
if axis != "*":
|
| 25 |
+
is_valid, reason = ParsedExpression.check_axis_name_return_reason(axis)
|
| 26 |
+
if not is_valid:
|
| 27 |
+
raise EinopsError(f'Invalid axis name {axis} in {opname}(..., "{pattern}")')
|
| 28 |
+
n_axes_before = axes.index("*")
|
| 29 |
+
n_axes_after = len(axes) - n_axes_before - 1
|
| 30 |
+
min_axes = n_axes_before + n_axes_after
|
| 31 |
+
return n_axes_before, n_axes_after, min_axes
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def pack(tensors: Sequence[Tensor], pattern: str) -> Tuple[Tensor, List[Shape]]:
|
| 35 |
+
"""
|
| 36 |
+
Packs several tensors into one.
|
| 37 |
+
See einops tutorial for introduction into packing (and how it replaces stack and concatenation).
|
| 38 |
+
|
| 39 |
+
Parameters:
|
| 40 |
+
tensors: tensors to be packed, can be of different dimensionality
|
| 41 |
+
pattern: pattern that is shared for all inputs and output, e.g. "i j * k" or "batch seq *"
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
(packed_tensor, packed_shapes aka PS)
|
| 45 |
+
|
| 46 |
+
Example:
|
| 47 |
+
```python
|
| 48 |
+
>>> from numpy import zeros as Z
|
| 49 |
+
>>> inputs = [Z([2, 3, 5]), Z([2, 3, 7, 5]), Z([2, 3, 7, 9, 5])]
|
| 50 |
+
>>> packed, ps = pack(inputs, 'i j * k')
|
| 51 |
+
>>> packed.shape, ps
|
| 52 |
+
((2, 3, 71, 5), [(), (7,), (7, 9)])
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
In this example, axes were matched to: i=2, j=3, k=5 based on order (first, second, and last).
|
| 56 |
+
All other axes were 'packed' and concatenated.
|
| 57 |
+
PS (packed shapes) contains information about axes that were matched to '*' in every input.
|
| 58 |
+
Resulting tensor has as many elements as all inputs in total.
|
| 59 |
+
|
| 60 |
+
Packing can be reversed with unpack, which additionally needs PS (packed shapes) to reconstruct order.
|
| 61 |
+
|
| 62 |
+
```python
|
| 63 |
+
>>> inputs_unpacked = unpack(packed, ps, 'i j * k')
|
| 64 |
+
>>> [x.shape for x in inputs_unpacked]
|
| 65 |
+
[(2, 3, 5), (2, 3, 7, 5), (2, 3, 7, 9, 5)]
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Read the tutorial for introduction and application scenarios.
|
| 69 |
+
"""
|
| 70 |
+
n_axes_before, n_axes_after, min_axes = analyze_pattern(pattern, "pack")
|
| 71 |
+
|
| 72 |
+
# packing zero tensors is illegal
|
| 73 |
+
backend = get_backend(tensors[0])
|
| 74 |
+
|
| 75 |
+
reshaped_tensors: List[Tensor] = []
|
| 76 |
+
packed_shapes: List[Shape] = []
|
| 77 |
+
for i, tensor in enumerate(tensors):
|
| 78 |
+
shape = backend.shape(tensor)
|
| 79 |
+
if len(shape) < min_axes:
|
| 80 |
+
raise EinopsError(
|
| 81 |
+
f"packed tensor #{i} (enumeration starts with 0) has shape {shape}, "
|
| 82 |
+
f"while pattern {pattern} assumes at least {min_axes} axes"
|
| 83 |
+
)
|
| 84 |
+
axis_after_packed_axes = len(shape) - n_axes_after
|
| 85 |
+
packed_shapes.append(shape[n_axes_before:axis_after_packed_axes])
|
| 86 |
+
reshaped_tensors.append(backend.reshape(tensor, (*shape[:n_axes_before], -1, *shape[axis_after_packed_axes:])))
|
| 87 |
+
|
| 88 |
+
return backend.concat(reshaped_tensors, axis=n_axes_before), packed_shapes
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def prod(x: Shape) -> int:
|
| 92 |
+
result = 1
|
| 93 |
+
for i in x:
|
| 94 |
+
result *= i
|
| 95 |
+
return result
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def unpack(tensor: Tensor, packed_shapes: List[Shape], pattern: str) -> List[Tensor]:
|
| 99 |
+
"""
|
| 100 |
+
Unpacks a single tensor into several by splitting over a selected axes.
|
| 101 |
+
See einops tutorial for introduction into packing (and how it replaces stack and concatenation).
|
| 102 |
+
|
| 103 |
+
Parameters:
|
| 104 |
+
tensor: tensor to be unpacked
|
| 105 |
+
packed_shapes: packed_shapes (aka PS) is a list of shapes that take place of '*' in each output.
|
| 106 |
+
output will contain a single tensor for every provided shape
|
| 107 |
+
pattern: pattern that is shared for input and all outputs, e.g. "i j * k" or "batch seq *",
|
| 108 |
+
where * designates an axis to be unpacked
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
list of tensors
|
| 112 |
+
|
| 113 |
+
If framework supports views, results are views to the original tensor.
|
| 114 |
+
|
| 115 |
+
Example:
|
| 116 |
+
```python
|
| 117 |
+
>>> from numpy import zeros as Z
|
| 118 |
+
>>> inputs = [Z([2, 3, 5]), Z([2, 3, 7, 5]), Z([2, 3, 7, 9, 5])]
|
| 119 |
+
>>> packed, ps = pack(inputs, 'i j * k')
|
| 120 |
+
>>> packed.shape, ps
|
| 121 |
+
((2, 3, 71, 5), [(), (7,), (7, 9)])
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
In this example, axes were matched to: i=2, j=3, k=5 based on order (first, second, and last).
|
| 125 |
+
All other axes were 'packed' and concatenated.
|
| 126 |
+
PS (packed shapes) contains information about axes that were matched to '*' in every input.
|
| 127 |
+
Resulting tensor has as many elements as all inputs in total.
|
| 128 |
+
|
| 129 |
+
Packing can be reversed with unpack, which additionally needs PS (packed shapes) to reconstruct order.
|
| 130 |
+
|
| 131 |
+
```python
|
| 132 |
+
>>> inputs_unpacked = unpack(packed, ps, 'i j * k')
|
| 133 |
+
>>> [x.shape for x in inputs_unpacked]
|
| 134 |
+
[(2, 3, 5), (2, 3, 7, 5), (2, 3, 7, 9, 5)]
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
Read the tutorial for introduction and application scenarios.
|
| 138 |
+
"""
|
| 139 |
+
n_axes_before, n_axes_after, min_axes = analyze_pattern(pattern, opname="unpack")
|
| 140 |
+
|
| 141 |
+
backend = get_backend(tensor)
|
| 142 |
+
input_shape = backend.shape(tensor)
|
| 143 |
+
if len(input_shape) != n_axes_before + 1 + n_axes_after:
|
| 144 |
+
raise EinopsError(f"unpack(..., {pattern}) received input of wrong dim with shape {input_shape}")
|
| 145 |
+
|
| 146 |
+
unpacked_axis: int = n_axes_before
|
| 147 |
+
|
| 148 |
+
lengths_of_composed_axes: List[int] = [-1 if -1 in p_shape else prod(p_shape) for p_shape in packed_shapes]
|
| 149 |
+
|
| 150 |
+
n_unknown_composed_axes = sum(int(x == -1) for x in lengths_of_composed_axes)
|
| 151 |
+
if n_unknown_composed_axes > 1:
|
| 152 |
+
raise EinopsError(
|
| 153 |
+
f"unpack(..., {pattern}) received more than one -1 in {packed_shapes} and can't infer dimensions"
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
# following manipulations allow to skip some shape verifications
|
| 157 |
+
# and leave it to backends
|
| 158 |
+
|
| 159 |
+
# [[], [2, 3], [4], [-1, 5], [6]] < examples of packed_axis
|
| 160 |
+
# split positions when computed should be
|
| 161 |
+
# [0, 1, 7, 11, N-6 , N ], where N = length of axis
|
| 162 |
+
split_positions = [0] * len(packed_shapes) + [input_shape[unpacked_axis]]
|
| 163 |
+
if n_unknown_composed_axes == 0:
|
| 164 |
+
for i, x in enumerate(lengths_of_composed_axes[:-1]):
|
| 165 |
+
split_positions[i + 1] = split_positions[i] + x
|
| 166 |
+
else:
|
| 167 |
+
unknown_composed_axis: int = lengths_of_composed_axes.index(-1)
|
| 168 |
+
for i in range(unknown_composed_axis):
|
| 169 |
+
split_positions[i + 1] = split_positions[i] + lengths_of_composed_axes[i]
|
| 170 |
+
for j in range(unknown_composed_axis + 1, len(lengths_of_composed_axes))[::-1]:
|
| 171 |
+
split_positions[j] = split_positions[j + 1] - lengths_of_composed_axes[j]
|
| 172 |
+
|
| 173 |
+
shape_start = input_shape[:unpacked_axis]
|
| 174 |
+
shape_end = input_shape[unpacked_axis + 1 :]
|
| 175 |
+
slice_filler = (slice(None, None),) * unpacked_axis
|
| 176 |
+
try:
|
| 177 |
+
return [
|
| 178 |
+
backend.reshape(
|
| 179 |
+
# shortest way slice arbitrary axis
|
| 180 |
+
tensor[(*slice_filler, slice(split_positions[i], split_positions[i + 1]))],
|
| 181 |
+
(*shape_start, *element_shape, *shape_end),
|
| 182 |
+
)
|
| 183 |
+
for i, element_shape in enumerate(packed_shapes)
|
| 184 |
+
]
|
| 185 |
+
except Exception:
|
| 186 |
+
# this hits if there is an error during reshapes, which means passed shapes were incorrect
|
| 187 |
+
raise RuntimeError(
|
| 188 |
+
f'Error during unpack(..., "{pattern}"): could not split axis of size {split_positions[-1]}'
|
| 189 |
+
f" into requested {packed_shapes}"
|
| 190 |
+
)
|
build/torch-cuda/parsing.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import EinopsError
|
| 2 |
+
import keyword
|
| 3 |
+
import warnings
|
| 4 |
+
from typing import List, Optional, Set, Tuple, Union
|
| 5 |
+
|
| 6 |
+
_ellipsis: str = "…" # NB, this is a single unicode symbol. String is used as it is not a list, but can be iterated
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class AnonymousAxis(object):
|
| 10 |
+
"""Important thing: all instances of this class are not equal to each other"""
|
| 11 |
+
|
| 12 |
+
def __init__(self, value: str):
|
| 13 |
+
self.value = int(value)
|
| 14 |
+
if self.value <= 1:
|
| 15 |
+
if self.value == 1:
|
| 16 |
+
raise EinopsError("No need to create anonymous axis of length 1. Report this as an issue")
|
| 17 |
+
else:
|
| 18 |
+
raise EinopsError("Anonymous axis should have positive length, not {}".format(self.value))
|
| 19 |
+
|
| 20 |
+
def __repr__(self):
|
| 21 |
+
return "{}-axis".format(str(self.value))
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ParsedExpression:
|
| 25 |
+
"""
|
| 26 |
+
non-mutable structure that contains information about one side of expression (e.g. 'b c (h w)')
|
| 27 |
+
and keeps some information important for downstream
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, expression: str, *, allow_underscore: bool = False, allow_duplicates: bool = False):
|
| 31 |
+
self.has_ellipsis: bool = False
|
| 32 |
+
self.has_ellipsis_parenthesized: Optional[bool] = None
|
| 33 |
+
self.identifiers: Set[str] = set()
|
| 34 |
+
# that's axes like 2, 3, 4 or 5. Axes with size 1 are exceptional and replaced with empty composition
|
| 35 |
+
self.has_non_unitary_anonymous_axes: bool = False
|
| 36 |
+
# composition keeps structure of composite axes, see how different corner cases are handled in tests
|
| 37 |
+
self.composition: List[Union[List[str], str]] = []
|
| 38 |
+
if "." in expression:
|
| 39 |
+
if "..." not in expression:
|
| 40 |
+
raise EinopsError("Expression may contain dots only inside ellipsis (...)")
|
| 41 |
+
if str.count(expression, "...") != 1 or str.count(expression, ".") != 3:
|
| 42 |
+
raise EinopsError(
|
| 43 |
+
"Expression may contain dots only inside ellipsis (...); only one ellipsis for tensor "
|
| 44 |
+
)
|
| 45 |
+
expression = expression.replace("...", _ellipsis)
|
| 46 |
+
self.has_ellipsis = True
|
| 47 |
+
|
| 48 |
+
bracket_group: Optional[List[str]] = None
|
| 49 |
+
|
| 50 |
+
def add_axis_name(x):
|
| 51 |
+
if x in self.identifiers:
|
| 52 |
+
if not (allow_underscore and x == "_") and not allow_duplicates:
|
| 53 |
+
raise EinopsError('Indexing expression contains duplicate dimension "{}"'.format(x))
|
| 54 |
+
if x == _ellipsis:
|
| 55 |
+
self.identifiers.add(_ellipsis)
|
| 56 |
+
if bracket_group is None:
|
| 57 |
+
self.composition.append(_ellipsis)
|
| 58 |
+
self.has_ellipsis_parenthesized = False
|
| 59 |
+
else:
|
| 60 |
+
bracket_group.append(_ellipsis)
|
| 61 |
+
self.has_ellipsis_parenthesized = True
|
| 62 |
+
else:
|
| 63 |
+
is_number = str.isdecimal(x)
|
| 64 |
+
if is_number and int(x) == 1:
|
| 65 |
+
# handling the case of anonymous axis of length 1
|
| 66 |
+
if bracket_group is None:
|
| 67 |
+
self.composition.append([])
|
| 68 |
+
else:
|
| 69 |
+
pass # no need to think about 1s inside parenthesis
|
| 70 |
+
return
|
| 71 |
+
is_axis_name, reason = self.check_axis_name_return_reason(x, allow_underscore=allow_underscore)
|
| 72 |
+
if not (is_number or is_axis_name):
|
| 73 |
+
raise EinopsError("Invalid axis identifier: {}\n{}".format(x, reason))
|
| 74 |
+
if is_number:
|
| 75 |
+
x = AnonymousAxis(x)
|
| 76 |
+
self.identifiers.add(x)
|
| 77 |
+
if is_number:
|
| 78 |
+
self.has_non_unitary_anonymous_axes = True
|
| 79 |
+
if bracket_group is None:
|
| 80 |
+
self.composition.append([x])
|
| 81 |
+
else:
|
| 82 |
+
bracket_group.append(x)
|
| 83 |
+
|
| 84 |
+
current_identifier = None
|
| 85 |
+
for char in expression:
|
| 86 |
+
if char in "() ":
|
| 87 |
+
if current_identifier is not None:
|
| 88 |
+
add_axis_name(current_identifier)
|
| 89 |
+
current_identifier = None
|
| 90 |
+
if char == "(":
|
| 91 |
+
if bracket_group is not None:
|
| 92 |
+
raise EinopsError("Axis composition is one-level (brackets inside brackets not allowed)")
|
| 93 |
+
bracket_group = []
|
| 94 |
+
elif char == ")":
|
| 95 |
+
if bracket_group is None:
|
| 96 |
+
raise EinopsError("Brackets are not balanced")
|
| 97 |
+
self.composition.append(bracket_group)
|
| 98 |
+
bracket_group = None
|
| 99 |
+
elif str.isalnum(char) or char in ["_", _ellipsis]:
|
| 100 |
+
if current_identifier is None:
|
| 101 |
+
current_identifier = char
|
| 102 |
+
else:
|
| 103 |
+
current_identifier += char
|
| 104 |
+
else:
|
| 105 |
+
raise EinopsError("Unknown character '{}'".format(char))
|
| 106 |
+
|
| 107 |
+
if bracket_group is not None:
|
| 108 |
+
raise EinopsError('Imbalanced parentheses in expression: "{}"'.format(expression))
|
| 109 |
+
if current_identifier is not None:
|
| 110 |
+
add_axis_name(current_identifier)
|
| 111 |
+
|
| 112 |
+
def flat_axes_order(self) -> List:
|
| 113 |
+
result = []
|
| 114 |
+
for composed_axis in self.composition:
|
| 115 |
+
assert isinstance(composed_axis, list), "does not work with ellipsis"
|
| 116 |
+
for axis in composed_axis:
|
| 117 |
+
result.append(axis)
|
| 118 |
+
return result
|
| 119 |
+
|
| 120 |
+
def has_composed_axes(self) -> bool:
|
| 121 |
+
# this will ignore 1 inside brackets
|
| 122 |
+
for axes in self.composition:
|
| 123 |
+
if isinstance(axes, list) and len(axes) > 1:
|
| 124 |
+
return True
|
| 125 |
+
return False
|
| 126 |
+
|
| 127 |
+
@staticmethod
|
| 128 |
+
def check_axis_name_return_reason(name: str, allow_underscore: bool = False) -> Tuple[bool, str]:
|
| 129 |
+
if not str.isidentifier(name):
|
| 130 |
+
return False, "not a valid python identifier"
|
| 131 |
+
elif name[0] == "_" or name[-1] == "_":
|
| 132 |
+
if name == "_" and allow_underscore:
|
| 133 |
+
return True, ""
|
| 134 |
+
return False, "axis name should should not start or end with underscore"
|
| 135 |
+
else:
|
| 136 |
+
if keyword.iskeyword(name):
|
| 137 |
+
warnings.warn("It is discouraged to use axes names that are keywords: {}".format(name), RuntimeWarning)
|
| 138 |
+
if name in ["axis"]:
|
| 139 |
+
warnings.warn(
|
| 140 |
+
"It is discouraged to use 'axis' as an axis name " "and will raise an error in future",
|
| 141 |
+
FutureWarning,
|
| 142 |
+
)
|
| 143 |
+
return True, ""
|
| 144 |
+
|
| 145 |
+
@staticmethod
|
| 146 |
+
def check_axis_name(name: str) -> bool:
|
| 147 |
+
"""
|
| 148 |
+
Valid axes names are python identifiers except keywords,
|
| 149 |
+
and additionally should not start or end with underscore
|
| 150 |
+
"""
|
| 151 |
+
is_valid, _reason = ParsedExpression.check_axis_name_return_reason(name)
|
| 152 |
+
return is_valid
|