Skip to content

Commit dcce620

Browse files
committed
[FlagGems Operator Development Competition] Add avg_pool3d operator
1 parent 2b083bd commit dcce620

6 files changed

Lines changed: 521 additions & 0 deletions

File tree

benchmark/test_avg_pool3d.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import Generator
2+
3+
import pytest
4+
import torch
5+
6+
import flag_gems
7+
8+
from . import attri_util as attr_utils
9+
from . import performance_utils as utils
10+
11+
12+
class AvgPool3dBenchmark(utils.GenericBenchmark):
13+
def get_input_iter(self, cur_dtype) -> Generator:
14+
for config in AVGPOOL3D_BENCH_CONFIGS:
15+
yield from self.input_fn(config, cur_dtype, self.device)
16+
17+
18+
AVGPOOL3D_BENCH_CONFIGS = [
19+
((8, 16, 16, 32, 32), 2, 2, 0, False, True, None),
20+
((8, 32, 16, 32, 32), 3, 2, 1, False, True, None),
21+
((4, 32, 24, 40, 40), 3, 2, 1, False, False, None),
22+
(
23+
(4, 64, 32, 32, 32),
24+
(2, 3, 3),
25+
(1, 2, 2),
26+
(0, 1, 1),
27+
False,
28+
True,
29+
None,
30+
),
31+
((2, 64, 32, 64, 64), 3, 2, 1, True, True, None),
32+
((2, 32, 32, 64, 64), 2, 1, 0, False, True, 4),
33+
]
34+
35+
36+
def avg_pool3d_input_fn(config, dtype, device):
37+
(
38+
shape,
39+
kernel_size,
40+
stride,
41+
padding,
42+
ceil_mode,
43+
count_include_pad,
44+
divisor_override,
45+
) = config
46+
inp = utils.generate_tensor_input(shape, dtype, device)
47+
48+
yield inp, {
49+
"kernel_size": kernel_size,
50+
"stride": stride,
51+
"padding": padding,
52+
"ceil_mode": ceil_mode,
53+
"count_include_pad": count_include_pad,
54+
"divisor_override": divisor_override,
55+
}
56+
57+
58+
@pytest.mark.avg_pool3d
59+
def test_avg_pool3d():
60+
bench = AvgPool3dBenchmark(
61+
input_fn=avg_pool3d_input_fn,
62+
op_name="avg_pool3d",
63+
torch_op=torch.ops.aten.avg_pool3d,
64+
dtypes=attr_utils.FLOAT_DTYPES,
65+
)
66+
bench.set_gems(flag_gems.avg_pool3d)
67+
bench.run()

conf/operators.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,19 @@ ops:
708708
- NeuralNetwork
709709
stages:
710710
- stable: '4.1'
711+
- name: avg_pool3d
712+
description: |
713+
Applies 3D average-pooling operation in `kD \mul kH \mul kW` regions by step size `sD \mul sH \mul sW` steps.
714+
The number of output features is equal to the number of input planes.
715+
This is for the forward case.
716+
for:
717+
- avg_pool3d
718+
labels:
719+
- nn.functional
720+
kind:
721+
- NeuralNetwork
722+
stages:
723+
- beta: '5.1'
711724
- name: baddbmm
712725
description: |
713726
Performs a batch matrix-matrix product of matrices in `batch1` and `batch2`.

src/flag_gems/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def torch_ge(v):
100100
("arctanh_", arctanh_),
101101
("avg_pool2d", avg_pool2d),
102102
("avg_pool2d_backward", avg_pool2d_backward),
103+
("avg_pool3d", avg_pool3d),
103104
("baddbmm", baddbmm),
104105
("bernoulli_.float", bernoulli_),
105106
("bincount", bincount),

src/flag_gems/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
scaled_dot_product_attention_forward,
4242
)
4343
from flag_gems.ops.avg_pool2d import avg_pool2d, avg_pool2d_backward
44+
from flag_gems.ops.avg_pool3d import avg_pool3d
4445
from flag_gems.ops.baddbmm import baddbmm
4546
from flag_gems.ops.batch_norm import batch_norm, batch_norm_backward
4647
from flag_gems.ops.bernoulli_ import bernoulli_
@@ -388,6 +389,7 @@
388389
"atan2_out",
389390
"avg_pool2d",
390391
"avg_pool2d_backward",
392+
"avg_pool3d",
391393
"baddbmm",
392394
"batch_norm",
393395
"batch_norm_backward",

src/flag_gems/ops/avg_pool3d.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import logging
2+
3+
import torch
4+
import triton
5+
import triton.language as tl
6+
7+
from flag_gems.utils import libentry
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
def pool3d_output_size(
13+
in_size: int,
14+
kernel_size: int,
15+
stride: int,
16+
padding: int,
17+
ceil_mode: bool = False,
18+
) -> int:
19+
numerator = in_size + 2 * padding - kernel_size
20+
if ceil_mode:
21+
output_size = (numerator + stride - 1) // stride + 1
22+
if (output_size - 1) * stride >= in_size + padding:
23+
output_size -= 1
24+
else:
25+
output_size = numerator // stride + 1
26+
27+
return output_size
28+
29+
30+
@libentry()
31+
@triton.jit
32+
def avg_pool3d_forward_kernel(
33+
input_ptr,
34+
output_ptr,
35+
total_elements,
36+
in_c: tl.constexpr,
37+
in_d: tl.constexpr,
38+
in_h: tl.constexpr,
39+
in_w: tl.constexpr,
40+
out_d: tl.constexpr,
41+
out_h: tl.constexpr,
42+
out_w: tl.constexpr,
43+
kernel_d: tl.constexpr,
44+
kernel_h: tl.constexpr,
45+
kernel_w: tl.constexpr,
46+
stride_d: tl.constexpr,
47+
stride_h: tl.constexpr,
48+
stride_w: tl.constexpr,
49+
padding_d: tl.constexpr,
50+
padding_h: tl.constexpr,
51+
padding_w: tl.constexpr,
52+
CEIL_MODE: tl.constexpr,
53+
COUNT_INCLUDE_PAD: tl.constexpr,
54+
divisor_override,
55+
BLOCK_SIZE: tl.constexpr,
56+
):
57+
offsets = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
58+
mask = offsets < total_elements
59+
60+
ow = offsets % out_w
61+
oh = (offsets // out_w) % out_h
62+
od = (offsets // (out_h * out_w)) % out_d
63+
c = (offsets // (out_d * out_h * out_w)) % in_c
64+
n = offsets // (in_c * out_d * out_h * out_w)
65+
66+
id_start = od * stride_d - padding_d
67+
ih_start = oh * stride_h - padding_h
68+
iw_start = ow * stride_w - padding_w
69+
70+
acc = tl.zeros((BLOCK_SIZE,), dtype=tl.float32)
71+
count = tl.zeros((BLOCK_SIZE,), dtype=tl.int32)
72+
73+
for kd in tl.static_range(0, kernel_d):
74+
id_in = id_start + kd
75+
d_valid = (id_in >= 0) & (id_in < in_d)
76+
for kh in tl.static_range(0, kernel_h):
77+
ih_in = ih_start + kh
78+
dh_valid = d_valid & (ih_in >= 0) & (ih_in < in_h)
79+
for kw in tl.static_range(0, kernel_w):
80+
iw_in = iw_start + kw
81+
in_mask = mask & dh_valid & (iw_in >= 0) & (iw_in < in_w)
82+
input_offsets = (
83+
((n * in_c + c) * in_d + id_in) * in_h + ih_in
84+
) * in_w + iw_in
85+
vals = tl.load(input_ptr + input_offsets, mask=in_mask, other=0.0)
86+
acc += tl.where(in_mask, vals, 0.0)
87+
count += in_mask.to(tl.int32)
88+
89+
if divisor_override != 0:
90+
divisor = tl.full((BLOCK_SIZE,), divisor_override, dtype=tl.float32)
91+
elif COUNT_INCLUDE_PAD:
92+
if CEIL_MODE:
93+
d_count = tl.minimum(id_start + kernel_d, in_d + padding_d) - tl.maximum(
94+
id_start, -padding_d
95+
)
96+
h_count = tl.minimum(ih_start + kernel_h, in_h + padding_h) - tl.maximum(
97+
ih_start, -padding_h
98+
)
99+
w_count = tl.minimum(iw_start + kernel_w, in_w + padding_w) - tl.maximum(
100+
iw_start, -padding_w
101+
)
102+
d_count = tl.maximum(d_count, 0)
103+
h_count = tl.maximum(h_count, 0)
104+
w_count = tl.maximum(w_count, 0)
105+
divisor = (d_count * h_count * w_count).to(tl.float32)
106+
else:
107+
divisor = tl.full(
108+
(BLOCK_SIZE,), kernel_d * kernel_h * kernel_w, dtype=tl.float32
109+
)
110+
else:
111+
divisor = count.to(tl.float32)
112+
113+
output = tl.where(divisor != 0, acc / divisor, 0.0)
114+
tl.store(output_ptr + offsets, output.to(output_ptr.type.element_ty), mask=mask)
115+
116+
117+
def _triple(value, name):
118+
if isinstance(value, int):
119+
return value, value, value
120+
if isinstance(value, (list, tuple)) and len(value) == 3:
121+
return tuple(value)
122+
raise ValueError(f"{name} must be an int or a sequence of three ints")
123+
124+
125+
def _parse_pool_params(kernel_size, stride, padding):
126+
kernel_d, kernel_h, kernel_w = _triple(kernel_size, "kernel_size")
127+
128+
if stride is None or (isinstance(stride, (list, tuple)) and len(stride) == 0):
129+
stride_d, stride_h, stride_w = kernel_d, kernel_h, kernel_w
130+
else:
131+
stride_d, stride_h, stride_w = _triple(stride, "stride")
132+
133+
padding_d, padding_h, padding_w = _triple(padding, "padding")
134+
135+
if kernel_d <= 0 or kernel_h <= 0 or kernel_w <= 0:
136+
raise ValueError("kernel_size must be greater than zero")
137+
138+
if stride_d <= 0 or stride_h <= 0 or stride_w <= 0:
139+
raise ValueError("stride must be greater than zero")
140+
141+
if padding_d < 0 or padding_h < 0 or padding_w < 0:
142+
raise ValueError("padding must be non-negative")
143+
144+
if (
145+
padding_d > kernel_d // 2
146+
or padding_h > kernel_h // 2
147+
or padding_w > kernel_w // 2
148+
):
149+
raise ValueError("pad should be smaller than or equal to half of kernel size")
150+
151+
return (
152+
kernel_d,
153+
kernel_h,
154+
kernel_w,
155+
stride_d,
156+
stride_h,
157+
stride_w,
158+
padding_d,
159+
padding_h,
160+
padding_w,
161+
)
162+
163+
164+
def avg_pool3d(
165+
input: torch.Tensor,
166+
kernel_size,
167+
stride=None,
168+
padding=0,
169+
ceil_mode=False,
170+
count_include_pad=True,
171+
divisor_override=None,
172+
):
173+
logger.debug("GEMS AVG_POOL3D FORWARD")
174+
175+
if input.dim() not in (4, 5):
176+
raise ValueError("avg_pool3d expects 4D or 5D input")
177+
178+
if divisor_override is not None and divisor_override == 0:
179+
raise ValueError("divisor_override cannot be zero")
180+
181+
(
182+
kernel_d,
183+
kernel_h,
184+
kernel_w,
185+
stride_d,
186+
stride_h,
187+
stride_w,
188+
padding_d,
189+
padding_h,
190+
padding_w,
191+
) = _parse_pool_params(kernel_size, stride, padding)
192+
193+
squeeze_batch = input.dim() == 4
194+
if squeeze_batch:
195+
input = input.unsqueeze(0)
196+
197+
input = input.contiguous()
198+
in_n, in_c, in_d, in_h, in_w = input.shape
199+
200+
if in_d <= 0 or in_h <= 0 or in_w <= 0:
201+
raise ValueError("input non-batch dimensions must have positive length")
202+
203+
out_d = pool3d_output_size(in_d, kernel_d, stride_d, padding_d, ceil_mode)
204+
out_h = pool3d_output_size(in_h, kernel_h, stride_h, padding_h, ceil_mode)
205+
out_w = pool3d_output_size(in_w, kernel_w, stride_w, padding_w, ceil_mode)
206+
207+
if out_d <= 0 or out_h <= 0 or out_w <= 0:
208+
raise ValueError("calculated output size is too small")
209+
210+
output = torch.empty(
211+
(in_n, in_c, out_d, out_h, out_w), device=input.device, dtype=input.dtype
212+
)
213+
214+
if output.numel() == 0:
215+
return output.squeeze(0) if squeeze_batch else output
216+
217+
block_size = 64 if input.dtype in (torch.float32, torch.float64) else 256
218+
grid = (triton.cdiv(output.numel(), block_size),)
219+
220+
avg_pool3d_forward_kernel[grid](
221+
input,
222+
output,
223+
output.numel(),
224+
in_c,
225+
in_d,
226+
in_h,
227+
in_w,
228+
out_d,
229+
out_h,
230+
out_w,
231+
kernel_d,
232+
kernel_h,
233+
kernel_w,
234+
stride_d,
235+
stride_h,
236+
stride_w,
237+
padding_d,
238+
padding_h,
239+
padding_w,
240+
CEIL_MODE=ceil_mode,
241+
COUNT_INCLUDE_PAD=count_include_pad,
242+
divisor_override=divisor_override if divisor_override is not None else 0,
243+
BLOCK_SIZE=block_size,
244+
)
245+
246+
return output.squeeze(0) if squeeze_batch else output

0 commit comments

Comments
 (0)