Skip to content

Commit 037e075

Browse files
authored
Merge branch 'master' into fix/tensordescriptor
Signed-off-by: wuhulalala <114969601+wuhulalala@users.noreply.github.com>
2 parents 1421de1 + 23d3cf8 commit 037e075

162 files changed

Lines changed: 8079 additions & 5974 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

benchmark/conftest.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,19 @@ def pytest_addoption(parser):
137137
help="Specify the shape file name for benchmarks. If not specified, a default shape list will be used.",
138138
)
139139

140-
parser.addoption(
141-
"--record",
142-
action="store",
143-
default="none",
144-
required=False,
145-
choices=["none", "log"],
146-
help="Benchmark info recorded in log files or not",
147-
)
140+
try:
141+
parser.addoption(
142+
"--record",
143+
action="store",
144+
default="none",
145+
required=False,
146+
choices=["none", "log"],
147+
help="Benchmark info recorded in log files or not",
148+
)
149+
except ValueError:
150+
# Mixed test+benchmark pytest runs may already register --record in
151+
# tests/conftest.py. Reuse the existing option in that case.
152+
pass
148153

149154
parser.addoption(
150155
"--parallel",
@@ -158,11 +163,16 @@ def pytest_addoption(parser):
158163
),
159164
)
160165

161-
parser.addoption(
162-
"--collect-marks",
163-
action="store_true",
164-
help="Collect the tests with marker information without executing them",
165-
)
166+
try:
167+
parser.addoption(
168+
"--collect-marks",
169+
action="store_true",
170+
help="Collect the tests with marker information without executing them",
171+
)
172+
except ValueError:
173+
# Mixed test+benchmark pytest runs may already register this option in
174+
# tests/conftest.py. Reuse the existing option in that case.
175+
pass
166176

167177

168178
def pytest_configure(config):

benchmark/core_shapes.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,19 @@ MvAndOuterBenchmark:
111111
- [8192, 8192]
112112
- [10240, 10240] #from perf
113113

114+
mm_self_transpose:
115+
shapes:
116+
- [257, 96]
117+
- [384, 384]
118+
- [1536, 320]
119+
- [3072, 768]
120+
- [1024, 1024]
121+
- [4096, 1024]
122+
- [5333, 71]
123+
- [8192, 2048]
124+
- [10240, 4096]
125+
shape_desc: "M, K"
126+
114127
# NORM shapes can be either 3D or 4D:
115128
# - 3D shapes are represented as [batch_size, channels, hidden_size]
116129
# - 4D shapes are represented as [batch_size, channels, height, width]

benchmark/test_blas_perf.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@ def get_tflops(self, op, *args, **kwargs):
135135
return total_flops
136136

137137

138+
class MmSelfTransposeBenchmark(GenericBenchmark2DOnly):
139+
"""
140+
Benchmark for the mm(a, a.t()) fast path.
141+
"""
142+
143+
DEFAULT_METRICS = DEFAULT_METRICS[:] + ["tflops"]
144+
145+
def set_more_shapes(self):
146+
return None
147+
148+
def get_tflops(self, op, *args, **kwargs):
149+
m, k = args[0].shape
150+
return 2 * m * m * k
151+
152+
138153
def addmm_input_fn(b, m, n, k, cur_dtype, device, b_column_major):
139154
inp1 = torch.randn([m, k], dtype=cur_dtype, device=device)
140155
bias = torch.randn([m, n], dtype=cur_dtype, device=device)
@@ -184,6 +199,16 @@ def mm_input_fn(b, m, n, k, cur_dtype, device, b_column_major):
184199
yield inp1, inp2
185200

186201

202+
def torch_mm_self_transpose(inp):
203+
return torch.mm(inp, inp.t())
204+
205+
206+
def mm_self_transpose_input_fn(shape, cur_dtype, device):
207+
m, k = shape
208+
inp = torch.randn([k, m], dtype=cur_dtype, device=device).t()
209+
yield inp,
210+
211+
187212
def group_mm_input_fn(groups, N, K, cur_dtype, device):
188213
assert cur_dtype == torch.bfloat16
189214
import random
@@ -439,6 +464,17 @@ def test_mv_and_outer_benchmark(op_name, torch_op, input_fn):
439464
bench.run()
440465

441466

467+
@pytest.mark.mm
468+
def test_mm_self_transpose_benchmark():
469+
bench = MmSelfTransposeBenchmark(
470+
input_fn=mm_self_transpose_input_fn,
471+
op_name="mm_self_transpose",
472+
torch_op=torch_mm_self_transpose,
473+
dtypes=FLOAT_DTYPES,
474+
)
475+
bench.run()
476+
477+
442478
class AddmvBenchmark(GenericBenchmark2DOnly):
443479
"""
444480
Benchmark for addmv

benchmark/test_clip_perf.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
import torch
3+
4+
from benchmark.attri_util import FLOAT_DTYPES
5+
from benchmark.conftest import BenchLevel, Config
6+
from benchmark.performance_utils import GenericBenchmark, generate_tensor_input
7+
8+
9+
def clip_input_fn(shape, cur_dtype, device):
10+
inp = generate_tensor_input(shape, cur_dtype, device)
11+
yield inp, -0.5, 0.5
12+
if Config.bench_level == BenchLevel.COMPREHENSIVE:
13+
yield inp, None, 0.5
14+
yield inp, -0.5, None
15+
16+
17+
@pytest.mark.clip
18+
def test_clip():
19+
bench = GenericBenchmark(
20+
input_fn=clip_input_fn,
21+
op_name="clip",
22+
torch_op=torch.clip,
23+
dtypes=FLOAT_DTYPES,
24+
)
25+
bench.run()
26+
27+
28+
@pytest.mark.clip_
29+
def test_clip_inplace():
30+
bench = GenericBenchmark(
31+
input_fn=clip_input_fn,
32+
op_name="clip_",
33+
torch_op=torch.clip_,
34+
dtypes=FLOAT_DTYPES,
35+
is_inplace=True,
36+
)
37+
bench.run()

src/flag_gems/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ def torch_ge(v):
129129
("clamp_", clamp_),
130130
("clamp_.Tensor", clamp_tensor_),
131131
("clamp_min_", clamp_min_),
132+
("clip", clip),
133+
("clip_", clip_),
132134
("conj_physical", conj_physical),
133135
("constant_pad_nd", constant_pad_nd),
134136
# ("contiguous", contiguous),

src/flag_gems/ops/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
clamp_tensor,
7272
clamp_tensor_,
7373
)
74+
from flag_gems.ops.clip import clip, clip_
7475
from flag_gems.ops.conj_physical import conj_physical
7576
from flag_gems.ops.contiguous import contiguous
7677
from flag_gems.ops.conv1d import conv1d
@@ -410,6 +411,8 @@
410411
"clamp_min_",
411412
"clamp_tensor",
412413
"clamp_tensor_",
414+
"clip",
415+
"clip_",
413416
"constant_pad_nd",
414417
"contiguous",
415418
"conv1d",

src/flag_gems/ops/clip.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import logging
2+
3+
import triton
4+
import triton.language as tl
5+
6+
from flag_gems.utils import pointwise_dynamic
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
@pointwise_dynamic(
12+
is_tensor=[True, False, False], promotion_methods=[(0, 1, 2, "DEFAULT")]
13+
)
14+
@triton.jit
15+
def clip_func(x, mini, maxi):
16+
return tl.minimum(maxi, tl.maximum(mini, x.to(tl.float32)))
17+
18+
19+
@pointwise_dynamic(is_tensor=[True, False], promotion_methods=[(0, 1, "DEFAULT")])
20+
@triton.jit
21+
def clip_func_min(x, mini):
22+
return tl.maximum(mini, x.to(tl.float32))
23+
24+
25+
@pointwise_dynamic(is_tensor=[True, False], promotion_methods=[(0, 1, "DEFAULT")])
26+
@triton.jit
27+
def clip_func_max(x, maxi):
28+
return tl.minimum(maxi, x.to(tl.float32))
29+
30+
31+
def clip(A, mini=None, maxi=None):
32+
logger.debug("GEMS CLIP")
33+
if mini is None and maxi is None:
34+
raise ValueError("At least one of mini or maxi must not be None")
35+
elif mini is None:
36+
return clip_func_max(A, maxi)
37+
elif maxi is None:
38+
return clip_func_min(A, mini)
39+
else:
40+
return clip_func(A, mini, maxi)
41+
42+
43+
def clip_(A, mini=None, maxi=None):
44+
logger.debug("GEMS CLIP_")
45+
if mini is None and maxi is None:
46+
raise ValueError("At least one of mini or maxi must not be None")
47+
elif mini is None:
48+
return clip_func_max(A, maxi, out0=A)
49+
elif maxi is None:
50+
return clip_func_min(A, mini, out0=A)
51+
else:
52+
return clip_func(A, mini, maxi, out0=A)

src/flag_gems/ops/mm.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,117 @@ def general_mm(a, b, c, M, N, K):
167167
return c
168168

169169

170+
@libentry()
171+
@libtuner(
172+
configs=runtime.get_tuned_config("mm_self_transpose"),
173+
key=["M", "K", "stride_am", "stride_ak"],
174+
strategy=["align32", "align32", "align32", "align32"],
175+
warmup=2,
176+
rep=4,
177+
)
178+
@triton.jit
179+
def mm_kernel_syrk(
180+
A,
181+
C,
182+
M,
183+
K,
184+
stride_am,
185+
stride_ak,
186+
stride_cm,
187+
stride_cn,
188+
BLOCK_M: tl.constexpr,
189+
BLOCK_K: tl.constexpr,
190+
):
191+
pid = tl.program_id(0)
192+
193+
# Packed lower-triangular launch domain:
194+
# pid = row * (row + 1) / 2 + col, where 0 <= col <= row.
195+
#
196+
# Invert the triangular-number indexing by solving:
197+
# row^2 + row - 2 * pid = 0
198+
# => row = (-1 + sqrt(1 + 8 * pid)) / 2
199+
#
200+
# We take floor(...) as the candidate row, then apply an integer +/-1 correction
201+
# because fp32 sqrt can be off near triangular-number boundaries.
202+
pid_f = pid.to(tl.float32)
203+
pid_m = tl.floor((tl.sqrt(8.0 * pid_f + 1.0) - 1.0) / 2.0).to(tl.int32)
204+
tri_start = pid_m * (pid_m + 1) // 2
205+
pid_m = tl.where(tri_start > pid, pid_m - 1, pid_m)
206+
next_tri_start = (pid_m + 1) * (pid_m + 2) // 2
207+
pid_m = tl.where(next_tri_start <= pid, pid_m + 1, pid_m)
208+
tri_start = pid_m * (pid_m + 1) // 2
209+
pid_n = pid - tri_start
210+
211+
rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
212+
rn = pid_n * BLOCK_M + tl.arange(0, BLOCK_M)
213+
ram = tl.max_contiguous(tl.multiple_of(rm % M, BLOCK_M), BLOCK_M).to(tl.int64)
214+
ran = tl.max_contiguous(tl.multiple_of(rn % M, BLOCK_M), BLOCK_M).to(tl.int64)
215+
rm = rm.to(tl.int64)
216+
rn = rn.to(tl.int64)
217+
acc = tl.zeros((BLOCK_M, BLOCK_M), dtype=tl.float32)
218+
219+
for start_k in range(0, K, BLOCK_K):
220+
rk = (start_k + tl.arange(0, BLOCK_K)).to(tl.int64)
221+
mask_k = rk < K
222+
a = tl.load(
223+
A + (ram[:, None] * stride_am + rk[None, :] * stride_ak),
224+
mask=mask_k[None, :],
225+
other=0.0,
226+
)
227+
b = tl.load(
228+
A + (rk[:, None] * stride_ak + ran[None, :] * stride_am),
229+
mask=mask_k[:, None],
230+
other=0.0,
231+
)
232+
acc += tl.dot(a, b, out_dtype=tl.float32, allow_tf32=False)
233+
234+
out = acc.to(C.dtype.element_ty)
235+
c_ptr = C + (rm[:, None] * stride_cm + rn[None, :] * stride_cn)
236+
mask = (rm < M)[:, None] & (rn < M)[None, :]
237+
tl.store(c_ptr, out, mask=mask)
238+
239+
if pid_m > pid_n:
240+
c_t_ptr = C + (rn[:, None] * stride_cm + rm[None, :] * stride_cn)
241+
mask_t = (rn < M)[:, None] & (rm < M)[None, :]
242+
tl.store(c_t_ptr, tl.trans(out), mask=mask_t)
243+
244+
245+
def is_syrk_transpose_pair(a, b):
246+
return (
247+
a.ndim == 2
248+
and b.ndim == 2
249+
and a.shape[0] == b.shape[1]
250+
and a.shape[1] == b.shape[0]
251+
and a.stride(0) == b.stride(1)
252+
and a.stride(1) == b.stride(0)
253+
and a.storage_offset() == b.storage_offset()
254+
and a.data_ptr() == b.data_ptr()
255+
)
256+
257+
258+
def syrk_mm(a, c, M, K):
259+
grid = lambda META: (
260+
# Number of tile rows is tiles = ceil(M / BLOCK_M).
261+
# Packed lower triangle contains:
262+
# 1 + 2 + ... + tiles = tiles * (tiles + 1) / 2
263+
triton.cdiv(M, META["BLOCK_M"])
264+
* (triton.cdiv(M, META["BLOCK_M"]) + 1)
265+
// 2,
266+
)
267+
with torch_device_fn.device(a.device):
268+
mm_kernel_syrk[grid](
269+
a,
270+
c,
271+
M,
272+
K,
273+
a.stride(0),
274+
a.stride(1),
275+
c.stride(0),
276+
c.stride(1),
277+
)
278+
return c
279+
280+
170281
def streamk_scenario(a, b, M, N, K):
171282
# TODO: this my change sometime according to the realbenchmark result
172283
# Currently, the best configuration for streamk has only been tested on A100(capability[0] == 8).
@@ -185,6 +296,10 @@ def streamk_scenario(a, b, M, N, K):
185296

186297
def mm(a, b):
187298
device = a.device
299+
if is_syrk_transpose_pair(a, b):
300+
M, K = a.shape
301+
c = torch.empty((M, M), device=device, dtype=a.dtype)
302+
return syrk_mm(a, c, M, K)
188303
# handle non-contiguous inputs if necessary
189304
if a.stride(0) > 1 and a.stride(1) > 1:
190305
a = a.contiguous()
@@ -206,6 +321,9 @@ def mm(a, b):
206321

207322

208323
def mm_out(a, b, *, out):
324+
if is_syrk_transpose_pair(a, b):
325+
M, K = a.shape
326+
return syrk_mm(a, out, M, K)
209327
# handle non-contiguous inputs if necessary
210328
if a.stride(0) > 1 and a.stride(1) > 1:
211329
a = a.contiguous()

0 commit comments

Comments
 (0)