Skip to content

Commit 1bfff66

Browse files
XDYuanzhuLeeSchopenhauer-loves-HegelYangyang0906CkkkwbDongxu-H
authored
[KernelGen][Nvidia] Add _adaptive_avg_pool3d_backward operator with Triton kernel (flagos-ai#3790)
* [KernelGen][Nvidia] Add _adaptive_avg_pool3d_backward operator with Triton kernel * Remove copyright notices from _adaptive_avg_pool3d_backward.py Removed copyright comments for Yuanzhu Li and BAAI. Signed-off-by: XDYuanzhuLee <93123184+XDYuanzhuLee@users.noreply.github.com> * Fix: move grad_output to GPU before passing to Triton kernel grad_output was created from a CPU tensor (ref_out), so it was on CPU. Triton kernels cannot access CPU tensors. * Update operator alpha stage to 5.3 * Update operator stage to alpha 5.4 * [KernelGen][Nvidia] Add registration trio for _adaptive_avg_pool3d_backward * fix: clean up adaptive_avg_pool3d_backward verification findings * fix: clear remaining adaptive_avg_pool3d_backward warnings * [KernelGen][Nvidia] Fix _adaptive_avg_pool3d_backward review comments * fix: sort ops exports * fix(_adaptive_avg_pool3d_backward): register _adaptive_avg_pool3d_backward as a standalone entry in operators.yaml --------- Signed-off-by: XDYuanzhuLee <93123184+XDYuanzhuLee@users.noreply.github.com> Signed-off-by: Dongxu-H <dxhan@baai.ac.cn> Co-authored-by: taooo <gumptao2997@gmail.com> Co-authored-by: yangy0906 <yangyang0906c@163.com> Co-authored-by: kkkwb <1115095230@qq.com> Co-authored-by: Dongxu-H <dxhan@baai.ac.cn>
1 parent e824728 commit 1bfff66

7 files changed

Lines changed: 374 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
import torch
3+
4+
from . import base, consts
5+
6+
# Shapes for adaptive_avg_pool3d backward benchmark
7+
ADAPTIVE_AVG_POOL3D_BACKWARD_SHAPES = [
8+
(1, 3, 8, 8, 8),
9+
(2, 3, 16, 16, 16),
10+
(1, 1, 32, 32, 32),
11+
(4, 8, 64, 64, 64),
12+
]
13+
14+
15+
class AdaptiveAvgPool3DBackwardBenchmark(base.Benchmark):
16+
def set_shapes(self, shape_file_path=None):
17+
self.shapes = ADAPTIVE_AVG_POOL3D_BACKWARD_SHAPES
18+
self.output_sizes = [(4, 4, 4), (8, 8, 8), (16, 16, 16), (32, 32, 32)]
19+
20+
def get_input_iter(self, cur_dtype):
21+
for shape, output_size in zip(self.shapes, self.output_sizes):
22+
x = torch.randn(shape, dtype=cur_dtype, device=self.device)
23+
# Compute forward to get output shape
24+
out = torch.nn.functional.adaptive_avg_pool3d(x, output_size)
25+
grad = torch.ones_like(out)
26+
yield grad, x
27+
28+
29+
@pytest.mark.adaptive_avg_pool3d_backward
30+
def test_adaptive_avg_pool3d_backward():
31+
bench = AdaptiveAvgPool3DBackwardBenchmark(
32+
op_name="adaptive_avg_pool3d_backward",
33+
torch_op=torch.ops.aten._adaptive_avg_pool3d_backward,
34+
dtypes=consts.FLOAT_DTYPES,
35+
)
36+
bench.run()

conf/operators.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,32 @@ ops:
183183
- NeuralNetwork
184184
stages:
185185
- alpha: '5.4'
186+
- id: _adaptive_avg_pool3d_backward
187+
description: |
188+
Computes the gradient of 3D adaptive average pooling.
189+
for:
190+
- _adaptive_avg_pool3d_backward
191+
labels:
192+
- aten
193+
- KernelGen
194+
- reduction
195+
kind:
196+
- NeuralNetwork
197+
stages:
198+
- alpha: '5.1'
199+
- id: adaptive_avg_pool3d_backward
200+
description: |
201+
Computes the gradient of 3D adaptive average pooling.
202+
for:
203+
- adaptive_avg_pool3d_backward
204+
labels:
205+
- aten
206+
- KernelGen
207+
- reduction
208+
kind:
209+
- NeuralNetwork
210+
stages:
211+
- alpha: '5.1'
186212
- id: add
187213
description: |
188214
Add a scalar or tensor to `self` tensor. If both `alpha` and `other` are specified,

src/flag_gems/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def torch_ge(v):
9191
("__xor__.Tensor", xor),
9292
("_adaptive_avg_pool2d", adaptive_avg_pool2d),
9393
("_adaptive_avg_pool2d_backward", _adaptive_avg_pool2d_backward),
94+
("_adaptive_avg_pool3d_backward", _adaptive_avg_pool3d_backward),
9495
("_add_relu.Tensor", _add_relu),
9596
(
9697
"_amp_foreach_non_finite_check_and_unscale_",
@@ -239,6 +240,7 @@ def torch_ge(v):
239240
("acos", acos),
240241
("acosh", acosh),
241242
("acosh_", acosh_),
243+
("adaptive_avg_pool3d_backward", _adaptive_avg_pool3d_backward),
242244
("adaptive_max_pool2d_backward", adaptive_max_pool2d_backward),
243245
("adaptive_max_pool3d_backward", adaptive_max_pool3d_backward),
244246
("add.Tensor", add),

src/flag_gems/ops/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
from flag_gems.ops.acos import acos
125125
from flag_gems.ops.acosh import acosh, acosh_
126126
from flag_gems.ops.adaptive_avg_pool2d import adaptive_avg_pool2d
127+
from flag_gems.ops.adaptive_avg_pool3d_backward import _adaptive_avg_pool3d_backward
127128
from flag_gems.ops.adaptive_max_pool2d_backward import adaptive_max_pool2d_backward
128129
from flag_gems.ops.adaptive_max_pool3d_backward import adaptive_max_pool3d_backward
129130
from flag_gems.ops.add import add, add_
@@ -836,6 +837,7 @@
836837
"__irshift__",
837838
"__lshift__",
838839
"_adaptive_avg_pool2d_backward",
840+
"_adaptive_avg_pool3d_backward",
839841
"_add_relu",
840842
"_amp_foreach_non_finite_check_and_unscale_",
841843
"_assert_async",
@@ -912,6 +914,7 @@
912914
"acosh",
913915
"acosh_",
914916
"adaptive_avg_pool2d",
917+
"adaptive_avg_pool3d_backward",
915918
"adaptive_max_pool2d_backward",
916919
"adaptive_max_pool3d_backward",
917920
"add",
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Copyright 2026, The FlagOS Contributors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
# Generated by KernelGen: https://github.com/flagos-ai/KernelGen
16+
import logging
17+
18+
import torch
19+
import triton
20+
import triton.language as tl
21+
22+
from flag_gems import runtime
23+
from flag_gems.utils import libentry
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
@libentry()
29+
@triton.autotune(
30+
configs=runtime.get_tuned_config("_adaptive_avg_pool3d_backward"),
31+
key=["n_elements"],
32+
)
33+
@triton.jit
34+
def _adaptive_avg_pool3d_backward_kernel(
35+
grad_output_ptr,
36+
grad_input_ptr,
37+
in_n,
38+
in_c,
39+
in_d,
40+
in_h,
41+
in_w,
42+
out_d,
43+
out_h,
44+
out_w,
45+
# Strides for grad_output
46+
out_stride_n,
47+
out_stride_c,
48+
out_stride_d,
49+
out_stride_h,
50+
out_stride_w,
51+
# Strides for grad_input
52+
grad_in_stride_n,
53+
grad_in_stride_c,
54+
grad_in_stride_d,
55+
grad_in_stride_h,
56+
grad_in_stride_w,
57+
n_elements: tl.constexpr,
58+
BLOCK_SIZE: tl.constexpr,
59+
MAX_OUT_D: tl.constexpr,
60+
MAX_OUT_H: tl.constexpr,
61+
MAX_OUT_W: tl.constexpr,
62+
):
63+
pid = tl.program_id(0)
64+
block_start = pid * BLOCK_SIZE
65+
offsets = block_start + tl.arange(0, BLOCK_SIZE)
66+
mask = offsets < n_elements
67+
68+
# Recover 5D coordinates for INPUT from flat index
69+
n = offsets // (in_c * in_d * in_h * in_w)
70+
remaining = offsets % (in_c * in_d * in_h * in_w)
71+
c = remaining // (in_d * in_h * in_w)
72+
remaining = remaining % (in_d * in_h * in_w)
73+
d_in = remaining // (in_h * in_w)
74+
remaining = remaining % (in_h * in_w)
75+
h_in = remaining // in_w
76+
w_in = remaining % in_w
77+
78+
# Initialize accumulator
79+
grad_acc = tl.zeros((BLOCK_SIZE,), dtype=tl.float32)
80+
81+
# Compute range of possible output positions for each dimension
82+
d_out_min = (d_in * out_d) // in_d
83+
d_out_max = ((d_in + 1) * out_d + in_d - 1) // in_d
84+
h_out_min = (h_in * out_h) // in_h
85+
h_out_max = ((h_in + 1) * out_h + in_h - 1) // in_h
86+
w_out_min = (w_in * out_w) // in_w
87+
w_out_max = ((w_in + 1) * out_w + in_w - 1) // in_w
88+
89+
# Clip to valid range
90+
d_out_max = tl.minimum(d_out_max, out_d)
91+
h_out_max = tl.minimum(h_out_max, out_h)
92+
w_out_max = tl.minimum(w_out_max, out_w)
93+
94+
# Iterate over possible output positions (use static range)
95+
# Upper bound: each input position can map to at most ceil(out/in) + 1 output
96+
# positions per dimension. Use a conservative constexpr computed host-side
97+
# so upsampling (out > in) is handled correctly.
98+
for d_out in tl.static_range(0, MAX_OUT_D):
99+
d_out_idx = d_out_min + d_out
100+
d_out_valid = (d_out_idx >= 0) & (d_out_idx < d_out_max)
101+
102+
for h_out in tl.static_range(0, MAX_OUT_H):
103+
h_out_idx = h_out_min + h_out
104+
h_out_valid = (h_out_idx >= 0) & (h_out_idx < h_out_max)
105+
106+
for w_out in tl.static_range(0, MAX_OUT_W):
107+
w_out_idx = w_out_min + w_out
108+
w_out_valid = (w_out_idx >= 0) & (w_out_idx < w_out_max)
109+
110+
out_valid = d_out_valid & h_out_valid & w_out_valid
111+
112+
# Compute kernel region for this output
113+
d_start = (d_out_idx * in_d) // out_d
114+
d_end = ((d_out_idx + 1) * in_d + out_d - 1) // out_d
115+
h_start = (h_out_idx * in_h) // out_h
116+
h_end = ((h_out_idx + 1) * in_h + out_h - 1) // out_h
117+
w_start = (w_out_idx * in_w) // out_w
118+
w_end = ((w_out_idx + 1) * in_w + out_w - 1) // out_w
119+
120+
d_end = tl.minimum(d_end, in_d)
121+
h_end = tl.minimum(h_end, in_h)
122+
w_end = tl.minimum(w_end, in_w)
123+
124+
# Check if current input is in this output's region
125+
in_region = (
126+
(d_in >= d_start)
127+
& (d_in < d_end)
128+
& (h_in >= h_start)
129+
& (h_in < h_end)
130+
& (w_in >= w_start)
131+
& (w_in < w_end)
132+
)
133+
134+
# Compute kernel size
135+
actual_kernel_d = d_end - d_start
136+
actual_kernel_h = h_end - h_start
137+
actual_kernel_w = w_end - w_start
138+
divisor = actual_kernel_d * actual_kernel_h * actual_kernel_w
139+
140+
# Load grad_output and accumulate
141+
grad_out_ptr = (
142+
grad_output_ptr
143+
+ n * out_stride_n
144+
+ c * out_stride_c
145+
+ d_out_idx * out_stride_d
146+
+ h_out_idx * out_stride_h
147+
+ w_out_idx * out_stride_w
148+
)
149+
grad_out_val = tl.load(grad_out_ptr, mask=mask & out_valid)
150+
151+
# Accumulate only if in_region
152+
contribution = tl.where(
153+
in_region, grad_out_val / tl.cast(divisor, tl.float32), 0.0
154+
)
155+
grad_acc += tl.where(out_valid, contribution, 0.0)
156+
157+
# Store result
158+
grad_in_ptr = (
159+
grad_input_ptr
160+
+ n * grad_in_stride_n
161+
+ c * grad_in_stride_c
162+
+ d_in * grad_in_stride_d
163+
+ h_in * grad_in_stride_h
164+
+ w_in * grad_in_stride_w
165+
)
166+
tl.store(grad_in_ptr, grad_acc, mask=mask)
167+
168+
169+
def _adaptive_avg_pool3d_backward(
170+
grad_output: torch.Tensor,
171+
input: torch.Tensor,
172+
):
173+
"""Gradient of adaptive_avg_pool3d backward."""
174+
logger.debug("GEMS _ADAPTIVE_AVG_POOL3D_BACKWARD")
175+
176+
# Get shapes
177+
in_n, in_c, in_d, in_h, in_w = input.shape
178+
out_n, out_c, out_d, out_h, out_w = grad_output.shape
179+
180+
# Allocate output
181+
grad_input = torch.zeros(
182+
(in_n, in_c, in_d, in_h, in_w),
183+
device=input.device,
184+
dtype=torch.float32,
185+
)
186+
187+
if grad_output.numel() == 0:
188+
return grad_input.to(grad_output.dtype)
189+
190+
n_elements = in_n * in_c * in_d * in_h * in_w
191+
192+
# Upper bound on the number of output positions a single input can map to
193+
# per dimension. For adaptive pooling, input i contributes to outputs in
194+
# [o_min, o_max) where o_max - o_min = ceil(out/in) (+1 at boundaries), so a
195+
# conservative constexpr is ceil(out / in) + 1. This covers upsampling.
196+
max_out_d = (out_d + in_d - 1) // in_d + 1
197+
max_out_h = (out_h + in_h - 1) // in_h + 1
198+
max_out_w = (out_w + in_w - 1) // in_w + 1
199+
200+
grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),)
201+
202+
_adaptive_avg_pool3d_backward_kernel[grid](
203+
grad_output,
204+
grad_input,
205+
in_n,
206+
in_c,
207+
in_d,
208+
in_h,
209+
in_w,
210+
out_d,
211+
out_h,
212+
out_w,
213+
grad_output.stride(0),
214+
grad_output.stride(1),
215+
grad_output.stride(2),
216+
grad_output.stride(3),
217+
grad_output.stride(4),
218+
grad_input.stride(0),
219+
grad_input.stride(1),
220+
grad_input.stride(2),
221+
grad_input.stride(3),
222+
grad_input.stride(4),
223+
n_elements,
224+
MAX_OUT_D=max_out_d,
225+
MAX_OUT_H=max_out_h,
226+
MAX_OUT_W=max_out_w,
227+
)
228+
229+
return grad_input.to(grad_output.dtype)

src/flag_gems/runtime/backend/_nvidia/tune_configs.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
_adaptive_avg_pool3d_backward:
16+
- META:
17+
BLOCK_SIZE: 512
18+
num_warps: 4
19+
num_stages: 3
20+
- META:
21+
BLOCK_SIZE: 1024
22+
num_warps: 4
23+
num_stages: 3
24+
- META:
25+
BLOCK_SIZE: 2048
26+
num_warps: 8
27+
num_stages: 3
28+
1529
attention:
1630
- gen: true
1731
param_map:

0 commit comments

Comments
 (0)