Skip to content

Commit c635e12

Browse files
authored
Update masked_select and nonzero (#719)
* Update masked_select and nonzero * add libentry to masked_select * update masked_select. * update masked_select * Update masked_select.py * Update masked_select.py * Update masked_select.py, adding back device_guard
1 parent 53a4372 commit c635e12

6 files changed

Lines changed: 196 additions & 30 deletions

File tree

benchmark/performance_utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,11 @@ def get_latency(self, op, *args, **kwargs):
258258
if self.is_backward:
259259
out = fn()
260260
dout = torch.randn_like(out)
261-
fn = lambda: out.backward(dout, retain_graph=True)
261+
# fn = lambda: out.backward(dout, retain_graph=True)
262+
xs = list(filter(lambda x: torch.is_tensor(x) and x.requires_grad, args))
263+
fn = lambda: torch.autograd.grad(
264+
(out,), xs, grad_outputs=(dout,), retain_graph=True
265+
)
262266
if Config.cpu_mode:
263267
for i in range(Config.warm_up):
264268
fn()
@@ -280,6 +284,7 @@ def get_latency(self, op, *args, **kwargs):
280284
warmup=Config.warm_up,
281285
rep=Config.repetition,
282286
return_mode="median",
287+
grad_to_none=xs if self.is_backward else None,
283288
)
284289
# average latency in ms
285290
return latency

benchmark/test_select_and_slice_perf.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def index_select_gbps(bench_fn_args, latency):
6666
return io_amount * 1e-9 / (latency * 1e-3)
6767

6868

69+
@pytest.mark.index_select
6970
@pytest.mark.parametrize(
7071
"op_name, torch_op, input_fn, gbps_fn, dtypes",
7172
[
@@ -77,6 +78,23 @@ def index_select_gbps(bench_fn_args, latency):
7778
FLOAT_DTYPES,
7879
marks=pytest.mark.index_select,
7980
),
81+
],
82+
)
83+
def test_perf_index_select(op_name, torch_op, input_fn, gbps_fn, dtypes):
84+
bench = TensorSelectBenchmark(
85+
input_fn=input_fn,
86+
op_name=op_name,
87+
torch_op=torch_op,
88+
dtypes=dtypes,
89+
get_gbps=gbps_fn,
90+
)
91+
bench.run()
92+
93+
94+
@pytest.mark.masked_select
95+
@pytest.mark.parametrize(
96+
"op_name, torch_op, input_fn, gbps_fn, dtypes",
97+
[
8098
pytest.param(
8199
"masked_select",
82100
torch.masked_select,
@@ -87,7 +105,7 @@ def index_select_gbps(bench_fn_args, latency):
87105
),
88106
],
89107
)
90-
def test_generic_reduction_benchmark(op_name, torch_op, input_fn, gbps_fn, dtypes):
108+
def test_perf_masked_select(op_name, torch_op, input_fn, gbps_fn, dtypes):
91109
bench = TensorSelectBenchmark(
92110
input_fn=input_fn,
93111
op_name=op_name,

src/flag_gems/ops/masked_select.py

Lines changed: 156 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,125 @@
44
import triton
55
import triton.language as tl
66

7-
from flag_gems import runtime
87
from flag_gems.runtime import torch_device_fn
98
from flag_gems.utils import broadcastable, libentry
10-
from flag_gems.utils import triton_lang_extension as tle
9+
from flag_gems.utils.shape_utils import bracket_next_power_of_2
1110

1211
logger = logging.getLogger(__name__)
1312

1413

1514
@libentry()
16-
@triton.autotune(configs=runtime.get_tuned_config("masked_select"), key=["n_elements"])
1715
@triton.jit
18-
def masked_select_kernel(
16+
def masked_select_single_pass_kernel(
17+
inp_ptr, mask_ptr, out_ptr, N, BLOCK_SIZE: tl.constexpr
18+
):
19+
pid = tl.program_id(0)
20+
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
21+
inp = tl.load(inp_ptr + offsets, mask=offsets < N)
22+
mask = tl.load(mask_ptr + offsets, mask=offsets < N).to(tl.int1)
23+
mask_ints = mask.to(tl.int32)
24+
out_offsets = tl.cumsum(mask_ints, axis=0) - 1
25+
26+
tl.store(out_ptr + out_offsets, inp, mask=offsets < N and mask)
27+
28+
29+
def masked_select_single_pass(inp, mask, out, N):
30+
BLOCK_SIZE = triton.next_power_of_2(N)
31+
if BLOCK_SIZE <= 512:
32+
num_warps = 4
33+
elif BLOCK_SIZE <= 2048:
34+
num_warps = 8
35+
else:
36+
num_warps = 16
37+
masked_select_single_pass_kernel[(1,)](
38+
inp, mask, out, N, BLOCK_SIZE=BLOCK_SIZE, num_warps=num_warps
39+
)
40+
return out
41+
42+
43+
@libentry()
44+
@triton.jit(do_not_specialize=["N", "nr", "row_stride"])
45+
def mask_part_sum_kernel(
1946
inp_ptr,
20-
select_mask_ptr,
21-
prefix_sum_ptr,
47+
mask_ptr,
48+
part_sums_ptr,
49+
counter_ptr,
50+
N,
51+
num_blocks,
52+
num_blocks_per_row,
53+
NP_BLOCK: tl.constexpr,
54+
BLOCK_SIZE: tl.constexpr,
55+
):
56+
row_id = tl.program_id(0)
57+
start_block = row_id * num_blocks_per_row
58+
offset = start_block * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
59+
acc = tl.zeros((BLOCK_SIZE,), dtype=part_sums_ptr.dtype.element_ty)
60+
61+
last_block_id = min(num_blocks - 1, start_block + num_blocks_per_row - 1)
62+
63+
for block_id in range(start_block, last_block_id):
64+
select = tl.load(mask_ptr + offset)
65+
select_ints = select.to(part_sums_ptr.dtype.element_ty)
66+
acc += select_ints
67+
offset += BLOCK_SIZE
68+
# Peeled last block
69+
select = tl.load(mask_ptr + offset, mask=offset < N, other=0)
70+
select_ints = select.to(part_sums_ptr.dtype.element_ty)
71+
acc += select_ints
72+
73+
part_sum = tl.sum(acc, axis=0)
74+
tl.store(part_sums_ptr + row_id, part_sum)
75+
# cumsum the part_sums
76+
count = tl.atomic_add(counter_ptr, 1, sem="acq_rel")
77+
np = tl.num_programs(0)
78+
if count == np - 1:
79+
mask = tl.arange(0, NP_BLOCK) < np
80+
part_sums = tl.load(part_sums_ptr + tl.arange(0, NP_BLOCK), mask=mask)
81+
final_sum = tl.sum(part_sums, axis=0)
82+
pre_sums = tl.cumsum(part_sums, axis=0)
83+
tl.store(
84+
part_sums_ptr + tl.arange(0, NP_BLOCK), pre_sums - part_sums, mask=mask
85+
)
86+
tl.store(part_sums_ptr + np, final_sum)
87+
88+
89+
@libentry()
90+
@triton.jit(do_not_specialize=["N", "nr", "row_stride"])
91+
def write_back_kernel(
92+
inp_ptr,
93+
mask_ptr,
94+
part_sums_ptr,
2295
out_ptr,
23-
n_elements,
96+
N,
97+
num_blocks,
98+
num_blocks_per_row,
99+
NP_BLOCK: tl.constexpr,
24100
BLOCK_SIZE: tl.constexpr,
25101
):
26-
pid = tle.program_id(axis=0)
27-
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
28-
mask = offsets < n_elements
102+
row_id = tl.program_id(0)
29103

30-
inp = tl.load(inp_ptr + offsets, mask=mask, other=0.0)
31-
select_mask = tl.load(select_mask_ptr + offsets, mask=mask, other=0.0).to(tl.int1)
32-
out_offset = tl.load(prefix_sum_ptr + offsets, mask=mask, other=0.0) - 1
104+
start_block = row_id * num_blocks_per_row
105+
offset = start_block * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
106+
advance = tl.load(part_sums_ptr + row_id)
33107

34-
tl.store(out_ptr + out_offset, inp, mask=(select_mask and mask))
108+
last_block_id = min(num_blocks - 1, start_block + num_blocks_per_row - 1)
109+
110+
for block_id in range(start_block, last_block_id):
111+
inp = tl.load(inp_ptr + offset)
112+
select_mask = tl.load(mask_ptr + offset).to(tl.int1)
113+
select_ints = select_mask.to(tl.constexpr(part_sums_ptr.dtype.element_ty))
114+
out_ptr += advance
115+
advance = tl.sum(select_ints, axis=0)
116+
pre_sums = tl.cumsum(select_ints, axis=0) - 1
117+
tl.store(out_ptr + pre_sums, inp, mask=select_mask)
118+
offset += BLOCK_SIZE
119+
# Peeled last block
120+
inp = tl.load(inp_ptr + offset, mask=offset < N)
121+
select_mask = tl.load(mask_ptr + offset, mask=offset < N, other=0).to(tl.int1)
122+
select_ints = select_mask.to(tl.constexpr(part_sums_ptr.dtype.element_ty))
123+
out_ptr += advance
124+
pre_sums = tl.cumsum(select_ints, axis=0) - 1
125+
tl.store(out_ptr + pre_sums, inp, mask=offset < N and select_mask)
35126

36127

37128
def masked_select(inp, mask):
@@ -48,13 +139,58 @@ def masked_select(inp, mask):
48139
inp = inp.contiguous()
49140
mask = mask.contiguous()
50141

51-
mask_flattened = mask.ravel()
142+
N = inp.numel()
143+
if N <= 4096:
144+
out = torch.empty(mask.sum(), dtype=inp.dtype, device=inp.device)
145+
return masked_select_single_pass(inp, mask, out, N)
146+
147+
# return mask_select(inp, mask)
148+
149+
BLOCK_SIZE = bracket_next_power_of_2(N, 128, 4096)
150+
num_warps = min(16, BLOCK_SIZE // 32)
52151

53-
prefix_sum = mask_flattened.cumsum(axis=0)
54-
out = torch.empty(prefix_sum[-1].item(), dtype=inp.dtype, device=inp.device)
152+
# max degree of parallelism
153+
np = torch_device_fn.get_device_properties(mask.device).multi_processor_count
154+
155+
# arranged as np rows of blocks
156+
n_blocks = triton.cdiv(N, BLOCK_SIZE)
157+
np = min(n_blocks, np)
158+
n_blocks_per_row = triton.cdiv(n_blocks, np)
159+
np = triton.cdiv(n_blocks, n_blocks_per_row)
160+
NP_BLOCK = triton.next_power_of_2(np)
55161

56-
n_elements = inp.numel()
57-
grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),)
58162
with torch_device_fn.device(inp.device):
59-
masked_select_kernel[grid](inp, mask_flattened, prefix_sum, out, n_elements)
163+
# Compute per cta sums and cumulative sums across ctas
164+
dtype = torch.int32 if N < 2**31 else torch.int64
165+
part_sums = torch.empty(np + 1, dtype=dtype, device=mask.device)
166+
barrier = torch.zeros([], dtype=torch.int, device=mask.device)
167+
mask_part_sum_kernel[(np,)](
168+
inp,
169+
mask,
170+
part_sums,
171+
barrier,
172+
N,
173+
n_blocks,
174+
n_blocks_per_row,
175+
NP_BLOCK=NP_BLOCK,
176+
BLOCK_SIZE=BLOCK_SIZE,
177+
num_warps=num_warps,
178+
)
179+
180+
# Write back selected data
181+
out = torch.empty(part_sums[-1], dtype=inp.dtype, device=mask.device)
182+
# write_offsets = pre_sums - part_sums
183+
write_back_kernel[(np,)](
184+
inp,
185+
mask,
186+
part_sums,
187+
out,
188+
N,
189+
n_blocks,
190+
n_blocks_per_row,
191+
NP_BLOCK=triton.next_power_of_2(np),
192+
BLOCK_SIZE=BLOCK_SIZE,
193+
num_warps=num_warps,
194+
)
195+
60196
return out

src/flag_gems/ops/nonzero.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@
1313

1414

1515
@libentry()
16-
@triton.autotune(
17-
configs=runtime.get_tuned_config("nonzero"),
18-
key=[
19-
"n_elements",
20-
],
21-
)
16+
@triton.heuristics(runtime.get_heuristic_config("elementwise_generic"))
2217
@triton.jit
2318
def nonzero_kernel(
2419
inp,
@@ -34,10 +29,10 @@ def nonzero_kernel(
3429
offset = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
3530
mask = offset < n_elements
3631

37-
inp_vals = tl.load(inp + offset, mask=mask)
32+
inp_vals = tl.load(inp + offset, mask=mask).to(tl.int1)
3833
out_offset = tl.load(prefix_sum + offset, mask=mask) - 1
3934

40-
nonzero_mask = mask and inp_vals == True # noqa
35+
nonzero_mask = mask and inp_vals # noqa
4136

4237
idx_flat = offset
4338
for dim in range(ndim - 1, -1, -1):

src/flag_gems/runtime/backend/_nvidia/heuristics_config_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
import triton
33

44

5+
def simple_elementwise_blocksize_heur(args):
6+
return 1024
7+
8+
59
def argmax_heur_block_m(args):
610
return 4 if args["M"] < 4096 else 8
711

@@ -302,4 +306,8 @@ def vdot_heur_block_size(args):
302306
"vdot": {
303307
"BLOCK_SIZE": vdot_heur_block_size,
304308
},
309+
"elementwise_generic": {
310+
"BLOCK_SIZE": simple_elementwise_blocksize_heur,
311+
"num_warps": lambda args: 8,
312+
},
305313
}

src/flag_gems/utils/shape_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
Perm = Tuple[int]
1717

1818

19+
def bracket_next_power_of_2(N, lower, upper):
20+
return min(max(triton.next_power_of_2(N), lower), upper)
21+
22+
1923
def broadcast(s1: Shape, s2: Shape) -> Shape:
2024
_s1, _s2 = s1, s2
2125
r1 = len(s1)

0 commit comments

Comments
 (0)