2024-08-19 11:22:28 +08:00
|
|
|
# modified from
|
|
|
|
# https://github.com/arogozhnikov/einops/blob/master/tests/test_examples.py
|
|
|
|
# https://github.com/arogozhnikov/einops/blob/master/tests/test_ops.py
|
|
|
|
# https://github.com/arogozhnikov/einops/blob/master/tests/test_parsing.py
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
import unittest
|
|
|
|
from tinygrad import Tensor
|
|
|
|
|
|
|
|
|
|
|
|
class test_rearrange_examples(unittest.TestCase):
|
|
|
|
def test1(self):
|
|
|
|
# transpose
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
y = x.rearrange("b c h w -> b h w c")
|
|
|
|
assert tuple(y.shape) == (10, 30, 40, 20)
|
|
|
|
|
|
|
|
def test2(self):
|
|
|
|
# view / reshape
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
y = x.rearrange("b c h w -> b (c h w)")
|
|
|
|
assert tuple(y.shape) == (10, 20 * 30 * 40)
|
|
|
|
|
|
|
|
def test3(self):
|
|
|
|
# depth-to-space
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
y = x.rearrange("b (c h1 w1) h w -> b c (h h1) (w w1)", h1=2, w1=2)
|
|
|
|
assert tuple(y.shape) == (10, 5, 30 * 2, 40 * 2)
|
|
|
|
|
|
|
|
def test4(self):
|
|
|
|
# space-to-depth
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
y = x.rearrange("b c (h h1) (w w1) -> b (h1 w1 c) h w", h1=2, w1=2)
|
|
|
|
assert tuple(y.shape) == (10, 20 * 4, 30 // 2, 40 // 2)
|
|
|
|
|
|
|
|
def test5(self):
|
|
|
|
# simple transposition
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
y = x.rearrange("b1 sound b2 letter -> b1 b2 sound letter")
|
|
|
|
assert tuple(y.shape) == (10, 30, 20, 40)
|
|
|
|
|
|
|
|
def test6(self):
|
|
|
|
# parsing parameters
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
t = x.rearrange("b c h w -> (b h w) c")
|
|
|
|
t = t[:, ::2] # replacement for dot-product, just changes size of second axis
|
|
|
|
assert tuple(t.shape) == (10 * 30 * 40, 10)
|
|
|
|
|
|
|
|
def test7(self):
|
|
|
|
x = Tensor(np.arange(10 * 20 * 30 * 40).reshape([10, 20, 30, 40]))
|
|
|
|
# split of embedding into groups
|
|
|
|
y1, y2 = x.rearrange("b (c g) h w -> g b c h w", g=2)
|
|
|
|
assert tuple(y1.shape) == (10, 10, 30, 40)
|
|
|
|
assert tuple(y2.shape) == (10, 10, 30, 40)
|
|
|
|
|
|
|
|
def test8(self):
|
|
|
|
x = Tensor(np.arange(10 * 20 * 1 * 1).reshape([10, 20, 1, 1]))
|
|
|
|
# squeeze - unsqueeze
|
|
|
|
y = x.rearrange("b c () () -> b c")
|
|
|
|
assert tuple(y.shape) == (10, 20)
|
|
|
|
y = y.rearrange("b c -> c b () ()")
|
|
|
|
assert tuple(y.shape) == (20, 10, 1, 1)
|
|
|
|
|
2024-08-20 02:19:07 +08:00
|
|
|
def test9(self):
|
|
|
|
x = Tensor(np.arange(10 * 20 * 1 * 1).reshape([10, 20, 1, 1]))
|
|
|
|
# squeeze - unsqueeze
|
|
|
|
y = x.rearrange("b c 1 1 -> b c")
|
|
|
|
assert tuple(y.shape) == (10, 20)
|
|
|
|
y = y.rearrange("b1 c -> c b1 1 1")
|
|
|
|
assert tuple(y.shape) == (20, 10, 1, 1)
|
|
|
|
|
2024-08-19 11:22:28 +08:00
|
|
|
def test_tensor_train_example_numpy(self):
|
|
|
|
# kept here just for a collection, only tested for numpy
|
|
|
|
# https://arxiv.org/pdf/1509.06569.pdf, (5)
|
|
|
|
x = Tensor.ones([3, 4, 5, 6])
|
|
|
|
rank = 4
|
|
|
|
|
|
|
|
# creating appropriate Gs
|
|
|
|
Gs = [Tensor.ones([d, d, rank, rank]) for d in x.shape]
|
|
|
|
Gs[0] = Gs[0][:, :, :1, :]
|
|
|
|
Gs[-1] = Gs[-1][:, :, :, :1]
|
|
|
|
|
|
|
|
# einsum way
|
|
|
|
y = x.reshape((1,) + x.shape)
|
|
|
|
for G in Gs:
|
|
|
|
# taking partial results left-to-right
|
|
|
|
# y = numpy.einsum('i j alpha beta, alpha i ... -> beta ... j', G, y)
|
|
|
|
y = Tensor(np.einsum("i j a b, a i ... -> b ... j", G.numpy(), y.numpy()))
|
|
|
|
y1 = y.reshape(-1)
|
|
|
|
|
|
|
|
# alternative way
|
|
|
|
y = x.reshape(-1)
|
|
|
|
for G in Gs:
|
|
|
|
i, j, alpha, beta = G.shape
|
|
|
|
y = y.rearrange("(i rest alpha) -> rest (alpha i)", alpha=alpha, i=i)
|
|
|
|
y = y @ G.rearrange("i j alpha beta -> (alpha i) (j beta)")
|
|
|
|
y = y.rearrange("rest (beta j) -> (beta rest j)", beta=beta, j=j)
|
|
|
|
y2 = y
|
|
|
|
assert np.allclose(y1.numpy(), y2.numpy())
|
|
|
|
|
|
|
|
# yet another way
|
|
|
|
y = x
|
|
|
|
for G in Gs:
|
|
|
|
i, j, alpha, beta = G.shape
|
|
|
|
y = y.rearrange("i ... (j alpha) -> ... j (alpha i)", alpha=alpha, i=i)
|
|
|
|
y = y @ G.rearrange("i j alpha beta -> (alpha i) (j beta)")
|
|
|
|
y3 = y.reshape(-1)
|
|
|
|
assert np.allclose(y1.numpy(), y3.numpy())
|
|
|
|
|
|
|
|
|
|
|
|
class test_rearrange_ops(unittest.TestCase):
|
|
|
|
def test_rearrange_errors(self):
|
|
|
|
x = Tensor.zeros([1, 1, 1, 1, 1])
|
|
|
|
x.rearrange("a b c d ... -> a b c ... d")
|
|
|
|
bad_patterns = [
|
|
|
|
"a b c d (...) -> a b c ... d", # collapsed ellipsis on input
|
|
|
|
"a b (c d ... -> a b c ... d", # unbalanced brackets
|
|
|
|
"a b* c d ... -> a b c ... d", # not alphanumeric
|
|
|
|
"a b c d -> a b c d -> a b c d", # two "->"
|
|
|
|
"a ... c ... -> ... a ... c", # two "..."
|
|
|
|
"a b c d e -> f b c d e", # name mismatch
|
|
|
|
]
|
|
|
|
for pattern in bad_patterns:
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
x.rearrange(pattern)
|
|
|
|
|
|
|
|
x.rearrange("... -> (...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
x.rearrange("(...) -> (...)")
|
|
|
|
|
|
|
|
y = Tensor.zeros([8, 1])
|
|
|
|
y.rearrange("(a1 a2 a3) b -> b a3 a2 a1", a1=2, a2=2)
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
|
|
## should fail as not enough dimensions specified
|
|
|
|
y.rearrange("(a1 a2 a3) b -> b a3 a2 a1", a1=2)
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
## should fail as 6 does not divide 8
|
|
|
|
y.rearrange("(a1 a2 a3) b -> b a3 a2 a1", a1=3, a2=2)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
## incorrect dimension provided for an axis that is only permuted
|
|
|
|
y.rearrange("(a1 a2 a3) b -> b a3 a2 a1", a1=2, a2=2, b=2)
|
2024-08-20 02:19:07 +08:00
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
## unused axis provided
|
|
|
|
y.rearrange("(a b c) d -> a b c d", b=2, c=2, e=2)
|
2024-08-19 11:22:28 +08:00
|
|
|
|
|
|
|
def test_rearrange_ellipsis_ops(self):
|
|
|
|
identity_patterns = [
|
|
|
|
"...->...",
|
|
|
|
"a b c d e-> a b c d e",
|
|
|
|
"a b c d e ...-> ... a b c d e",
|
|
|
|
"a b c d e ...-> a ... b c d e",
|
|
|
|
"... a b c d e -> ... a b c d e",
|
|
|
|
"a ... e-> a ... e",
|
|
|
|
"a ... -> a ... ",
|
|
|
|
"a ... c d e -> a (...) c d e",
|
|
|
|
]
|
|
|
|
|
|
|
|
equivalent_rearrange_patterns = [
|
|
|
|
("a b c d e -> (a b) c d e", "a b ... -> (a b) ... "),
|
|
|
|
("a b c d e -> a b (c d) e", "... c d e -> ... (c d) e"),
|
|
|
|
("a b c d e -> a b c d e", "... -> ... "),
|
|
|
|
("a b c d e -> (a b c d e)", "... -> (...)"),
|
|
|
|
("a b c d e -> b (c d e) a", "a b ... -> b (...) a"),
|
|
|
|
("a b c d e -> b (a c d) e", "a b ... e -> b (a ...) e"),
|
|
|
|
]
|
|
|
|
|
|
|
|
xnp = np.arange(2 * 3 * 4 * 5 * 6).reshape([2, 3, 4, 5, 6])
|
|
|
|
x = Tensor(xnp)
|
|
|
|
for pattern in identity_patterns:
|
|
|
|
assert np.array_equal(xnp, x.rearrange(pattern).numpy()), pattern
|
|
|
|
|
|
|
|
for pattern1, pattern2 in equivalent_rearrange_patterns:
|
|
|
|
assert np.array_equal(x.rearrange(pattern1).numpy(), x.rearrange(pattern2).numpy())
|
|
|
|
|
|
|
|
def test_rearrange_consistency(self):
|
|
|
|
shape = [1, 2, 3, 5, 7, 11]
|
|
|
|
xnp = np.arange(np.prod(shape)).reshape(shape)
|
|
|
|
x = Tensor(xnp)
|
|
|
|
for pattern in [
|
|
|
|
"a b c d e f -> a b c d e f",
|
|
|
|
"b a c d e f -> a b d e f c",
|
|
|
|
"a b c d e f -> f e d c b a",
|
|
|
|
"a b c d e f -> (f e) d (c b a)",
|
|
|
|
"a b c d e f -> (f e d c b a)",
|
|
|
|
]:
|
|
|
|
result = x.rearrange(pattern).numpy()
|
|
|
|
assert len(np.setdiff1d(xnp, result)) == 0
|
|
|
|
assert result.dtype == xnp.dtype
|
|
|
|
|
|
|
|
result = x.rearrange("a b c d e f -> a (b) (c d e) f").numpy()
|
|
|
|
assert np.array_equal(xnp.flatten(), result.flatten())
|
|
|
|
|
|
|
|
result = x.rearrange("a aa aa1 a1a1 aaaa a11 -> a aa aa1 a1a1 aaaa a11").numpy()
|
|
|
|
assert np.array_equal(xnp, result)
|
|
|
|
|
|
|
|
result1 = x.rearrange("a b c d e f -> f e d c b a").numpy()
|
|
|
|
result2 = x.rearrange("f e d c b a -> a b c d e f").numpy()
|
|
|
|
assert np.array_equal(result1, result2)
|
|
|
|
|
|
|
|
result = x.rearrange("a b c d e f -> (f d) c (e b) a").rearrange("(f d) c (e b) a -> a b c d e f", b=2, d=5).numpy()
|
|
|
|
assert np.array_equal(xnp, result)
|
|
|
|
|
|
|
|
sizes = dict(zip("abcdef", shape))
|
|
|
|
temp = x.rearrange("a b c d e f -> (f d) c (e b) a", **sizes)
|
|
|
|
result = temp.rearrange("(f d) c (e b) a -> a b c d e f", **sizes).numpy()
|
|
|
|
assert np.array_equal(xnp, result)
|
|
|
|
|
|
|
|
x2 = np.arange(2 * 3 * 4).reshape([2, 3, 4])
|
|
|
|
result = Tensor(x2).rearrange("a b c -> b c a").numpy()
|
|
|
|
assert x2[1, 2, 3] == result[2, 3, 1]
|
|
|
|
assert x2[0, 1, 2] == result[1, 2, 0]
|
|
|
|
|
|
|
|
def test_rearrange_permutations(self):
|
|
|
|
# tests random permutation of axes against two independent numpy ways
|
|
|
|
for n_axes in range(1, 10):
|
|
|
|
x = np.arange(2**n_axes).reshape([2] * n_axes)
|
|
|
|
permutation = np.random.permutation(n_axes)
|
|
|
|
left_expression = " ".join("i" + str(axis) for axis in range(n_axes))
|
|
|
|
right_expression = " ".join("i" + str(axis) for axis in permutation)
|
|
|
|
expression = left_expression + " -> " + right_expression
|
|
|
|
result = Tensor(x).rearrange(expression).numpy()
|
|
|
|
|
|
|
|
for pick in np.random.randint(0, 2, [10, n_axes]):
|
|
|
|
assert x[tuple(pick)] == result[tuple(pick[permutation])]
|
|
|
|
|
|
|
|
for n_axes in range(1, 10):
|
|
|
|
x = np.arange(2**n_axes).reshape([2] * n_axes)
|
|
|
|
permutation = np.random.permutation(n_axes)
|
|
|
|
left_expression = " ".join("i" + str(axis) for axis in range(n_axes)[::-1])
|
|
|
|
right_expression = " ".join("i" + str(axis) for axis in permutation[::-1])
|
|
|
|
expression = left_expression + " -> " + right_expression
|
|
|
|
result = Tensor(x).rearrange(expression).numpy()
|
|
|
|
assert result.shape == x.shape
|
|
|
|
expected_result = np.zeros_like(x)
|
|
|
|
for original_axis, result_axis in enumerate(permutation):
|
|
|
|
expected_result |= ((x >> original_axis) & 1) << result_axis
|
|
|
|
|
|
|
|
assert np.array_equal(result, expected_result)
|
|
|
|
|
|
|
|
def check_expression_helper(expression: str):
|
|
|
|
Tensor.ones((1, 2, 3, 4, 5, 6, 7))
|
|
|
|
|
|
|
|
class test_rearrange_parsing(unittest.TestCase):
|
|
|
|
def test_elementary_axis_name(self):
|
|
|
|
for name in [
|
|
|
|
"a",
|
|
|
|
"b",
|
|
|
|
"h",
|
|
|
|
"dx",
|
|
|
|
"h1",
|
|
|
|
"zz",
|
|
|
|
"i9123",
|
|
|
|
"somelongname",
|
|
|
|
"Alex",
|
|
|
|
"camelCase",
|
|
|
|
"u_n_d_e_r_score",
|
|
|
|
"unreasonablyLongAxisName",
|
|
|
|
]:
|
|
|
|
Tensor.ones((1,)).rearrange(f"{name} -> {name}")
|
|
|
|
|
|
|
|
for name in ["2b", "12", "_startWithUnderscore", "endWithUnderscore_", "_"]:
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
Tensor.ones((1,)).rearrange(f"{name} -> {name}")
|
|
|
|
|
|
|
|
with self.assertRaises(RuntimeError):
|
|
|
|
Tensor.ones((1,)).rearrange(" -> ")
|
|
|
|
|
|
|
|
def test_invalid_expressions(self):
|
|
|
|
# double ellipsis should raise an error
|
|
|
|
def _test_expression(expression: str):
|
|
|
|
Tensor.ones((2, 3, 4, 5, 6)).rearrange(f"{expression} -> {expression}")
|
|
|
|
|
|
|
|
_test_expression("... a b c d")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("... a b c d ...")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("... a b c (d ...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("(... a) b c (d ...)")
|
|
|
|
|
|
|
|
# double/missing/enclosed parenthesis
|
|
|
|
Tensor.ones((2, 3, 4, 5, 6)).rearrange("a b c d ... -> (a) b c (d ...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("(a)) b c (d ...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("(a b c (d ...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("(a) (()) b c (d ...)")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("(a) ((b c) (d ...))")
|
|
|
|
|
|
|
|
# invalid identifiers
|
|
|
|
_test_expression("camelCase under_scored cApiTaLs ß ...")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("1a")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("_pre")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("...pre")
|
|
|
|
with self.assertRaises(AssertionError):
|
|
|
|
_test_expression("pre...")
|
|
|
|
|
|
|
|
def test_unicode_ellipsis(self):
|
|
|
|
equivalent_rearrange_patterns = [
|
|
|
|
("a b … -> (a b) … ", "a b ... -> (a b) ... "),
|
|
|
|
("… c d e -> … (c d) e", "... c d e -> ... (c d) e"),
|
|
|
|
("… -> … ", "... -> ... "),
|
|
|
|
("… -> (…)", "... -> (...)"),
|
|
|
|
("a b … -> b (…) a", "a b ... -> b (...) a"),
|
|
|
|
("a b … e -> b (a …) e", "a b ... e -> b (a ...) e"),
|
|
|
|
]
|
|
|
|
|
|
|
|
xnp = np.arange(2 * 3 * 4 * 5 * 6).reshape([2, 3, 4, 5, 6])
|
|
|
|
x = Tensor(xnp)
|
|
|
|
|
|
|
|
for pattern1, pattern2 in equivalent_rearrange_patterns:
|
|
|
|
assert np.array_equal(x.rearrange(pattern1).numpy(), x.rearrange(pattern2).numpy())
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|