Skip to content

Commit 3388ad0

Browse files
Schopenhauer-loves-Hegelfactnnclaudetengqm
authored andcommitted
【KernelGen】Add einsum operator (flagos-ai#1731)
* feat: add einsum operator with tests and benchmark Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Qiming Teng <tengqm@outlook.com> Signed-off-by: Zang Peiyu <166481866+factnn@users.noreply.github.com> * fix: use consts.FLOAT_DTYPES and base.Benchmark in einsum benchmark * fix: fix einsum benchmark - use correct base classes and shapes --------- Signed-off-by: Zang Peiyu <166481866+factnn@users.noreply.github.com> Co-authored-by: factnn <1050552884@qq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Zang Peiyu <166481866+factnn@users.noreply.github.com> Co-authored-by: Qiming Teng <tengqm@outlook.com>
1 parent 0a2d219 commit 3388ad0

5 files changed

Lines changed: 801 additions & 0 deletions

File tree

benchmark/test_einsum.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from typing import Generator
2+
3+
import pytest
4+
import torch
5+
6+
from . import base, consts
7+
8+
9+
class EinsumBenchmark(base.Benchmark):
10+
DEFAULT_METRICS = consts.DEFAULT_METRICS[:] + ["tflops"]
11+
DEFAULT_SHAPES = [(1, 512, 512, 512), (1, 1024, 1024, 1024), (16, 512, 512, 512)]
12+
13+
def __init__(self, *args, batched=False, input_fn=None, **kwargs):
14+
self.batched = batched
15+
super().__init__(*args, **kwargs)
16+
17+
def set_more_shapes(self):
18+
return []
19+
20+
def set_shapes(self, *args, **kwargs):
21+
self.shapes = self.DEFAULT_SHAPES
22+
23+
def get_input_iter(self, dtype) -> Generator:
24+
for b, m, n, k in self.shapes:
25+
if self.batched:
26+
inp1 = torch.randn([b, m, k], dtype=dtype, device=self.device)
27+
inp2 = torch.randn([b, k, n], dtype=dtype, device=self.device)
28+
else:
29+
inp1 = torch.randn([m, k], dtype=dtype, device=self.device)
30+
inp2 = torch.randn([k, n], dtype=dtype, device=self.device)
31+
yield inp1, inp2
32+
33+
def get_tflops(self, op, *args, **kwargs):
34+
A, B = args[0], args[1]
35+
if self.batched:
36+
return A.shape[0] * A.shape[1] * B.shape[2] * A.shape[2] * 2
37+
return A.shape[0] * B.shape[1] * A.shape[1] * 2
38+
39+
40+
class EinsumGenericBenchmark(base.GenericBenchmark):
41+
def set_shapes(self, *args, **kwargs):
42+
pass # keep shapes set by caller
43+
44+
45+
def dot_input_fn(shape, dtype, device):
46+
(n,) = shape
47+
yield torch.randn(n, dtype=dtype, device=device), torch.randn(
48+
n, dtype=dtype, device=device
49+
)
50+
51+
52+
def outer_input_fn(shape, dtype, device):
53+
m, n = shape
54+
yield torch.randn(m, dtype=dtype, device=device), torch.randn(
55+
n, dtype=dtype, device=device
56+
)
57+
58+
59+
def unary_2d_input_fn(shape, dtype, device):
60+
m, n = shape
61+
yield (torch.randn(m, n, dtype=dtype, device=device),)
62+
63+
64+
def unary_3d_input_fn(shape, dtype, device):
65+
m, n, k = shape
66+
yield (torch.randn(m, n, k, dtype=dtype, device=device),)
67+
68+
69+
def ellipsis_input_fn(shape, dtype, device):
70+
b, h, m, k, n = shape
71+
yield torch.randn(b, h, m, k, dtype=dtype, device=device), torch.randn(
72+
b, h, k, n, dtype=dtype, device=device
73+
)
74+
75+
76+
@pytest.mark.einsum
77+
def test_einsum_matmul():
78+
bench = EinsumBenchmark(
79+
input_fn=None,
80+
op_name="einsum",
81+
torch_op=lambda A, B: torch.einsum("ij,jk->ik", A, B),
82+
dtypes=consts.FLOAT_DTYPES,
83+
)
84+
bench.run()
85+
86+
87+
@pytest.mark.einsum
88+
def test_einsum_bmm():
89+
bench = EinsumBenchmark(
90+
input_fn=None,
91+
op_name="einsum",
92+
torch_op=lambda A, B: torch.einsum("bij,bjk->bik", A, B),
93+
dtypes=consts.FLOAT_DTYPES,
94+
batched=True,
95+
)
96+
bench.run()
97+
98+
99+
@pytest.mark.einsum
100+
def test_einsum_dot():
101+
bench = EinsumGenericBenchmark(
102+
input_fn=dot_input_fn,
103+
op_name="einsum",
104+
torch_op=lambda A, B: torch.einsum("i,i->", A, B),
105+
dtypes=consts.FLOAT_DTYPES,
106+
)
107+
bench.shapes = [(1024,), (4096,), (65536,)]
108+
bench.run()
109+
110+
111+
@pytest.mark.einsum
112+
def test_einsum_outer():
113+
bench = EinsumGenericBenchmark(
114+
input_fn=outer_input_fn,
115+
op_name="einsum",
116+
torch_op=lambda A, B: torch.einsum("i,j->ij", A, B),
117+
dtypes=consts.FLOAT_DTYPES,
118+
)
119+
bench.shapes = [(1024, 1024), (4096, 4096)]
120+
bench.run()
121+
122+
123+
@pytest.mark.einsum
124+
def test_einsum_trace():
125+
bench = EinsumGenericBenchmark(
126+
input_fn=unary_2d_input_fn,
127+
op_name="einsum",
128+
torch_op=lambda A: torch.einsum("ii->", A),
129+
dtypes=consts.FLOAT_DTYPES,
130+
)
131+
bench.shapes = [(1024, 1024), (4096, 4096)]
132+
bench.run()
133+
134+
135+
@pytest.mark.einsum
136+
def test_einsum_diagonal():
137+
bench = EinsumGenericBenchmark(
138+
input_fn=unary_2d_input_fn,
139+
op_name="einsum",
140+
torch_op=lambda A: torch.einsum("ii->i", A),
141+
dtypes=consts.FLOAT_DTYPES,
142+
)
143+
bench.shapes = [(1024, 1024), (4096, 4096)]
144+
bench.run()
145+
146+
147+
@pytest.mark.einsum
148+
def test_einsum_transpose():
149+
bench = EinsumGenericBenchmark(
150+
input_fn=unary_2d_input_fn,
151+
op_name="einsum",
152+
torch_op=lambda A: torch.einsum("ij->ji", A),
153+
dtypes=consts.FLOAT_DTYPES,
154+
)
155+
bench.shapes = [(1024, 1024), (4096, 4096)]
156+
bench.run()
157+
158+
159+
@pytest.mark.einsum
160+
def test_einsum_sum_all():
161+
bench = EinsumGenericBenchmark(
162+
input_fn=unary_3d_input_fn,
163+
op_name="einsum",
164+
torch_op=lambda A: torch.einsum("ijk->", A),
165+
dtypes=consts.FLOAT_DTYPES,
166+
)
167+
bench.shapes = [(64, 64, 64), (128, 128, 128)]
168+
bench.run()
169+
170+
171+
@pytest.mark.einsum
172+
def test_einsum_sum_dim():
173+
bench = EinsumGenericBenchmark(
174+
input_fn=unary_3d_input_fn,
175+
op_name="einsum",
176+
torch_op=lambda A: torch.einsum("ijk->j", A),
177+
dtypes=consts.FLOAT_DTYPES,
178+
)
179+
bench.shapes = [(64, 64, 64), (128, 128, 128)]
180+
bench.run()
181+
182+
183+
@pytest.mark.einsum
184+
def test_einsum_ellipsis():
185+
bench = EinsumGenericBenchmark(
186+
input_fn=ellipsis_input_fn,
187+
op_name="einsum",
188+
torch_op=lambda A, B: torch.einsum("...ij,...jk->...ik", A, B),
189+
dtypes=consts.FLOAT_DTYPES,
190+
)
191+
bench.shapes = [(2, 4, 64, 64, 128), (2, 8, 128, 128, 256)]
192+
bench.run()

src/flag_gems/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ def torch_ge(v):
193193
("divide_.Tensor", true_divide_),
194194
("divide_.Tensor_mode", div_mode_),
195195
("dot", dot),
196+
("einsum", einsum),
196197
("elu", elu),
197198
("elu_", elu_),
198199
("elu_backward", elu_backward),

src/flag_gems/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
)
109109
from flag_gems.ops.dot import dot
110110
from flag_gems.ops.dropout import dropout, dropout_backward
111+
from flag_gems.ops.einsum import einsum
111112
from flag_gems.ops.elu import elu, elu_, elu_backward
112113
from flag_gems.ops.embedding import embedding, embedding_backward
113114
from flag_gems.ops.embedding_dense_backward import embedding_dense_backward
@@ -483,6 +484,7 @@
483484
"dot",
484485
"dropout",
485486
"dropout_backward",
487+
"einsum",
486488
"elu",
487489
"elu_",
488490
"elu_backward",

0 commit comments

Comments
 (0)