kernels-bot commited on
Commit
ae022fa
·
verified ·
1 Parent(s): cb47c7f

Uploaded using `kernel-builder`.

Browse files
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