Skip to content

Commit aca87de

Browse files
committed
[KernelGen][MThreads] Add adaptive_max_pool3d_backward Moore Threads specialized operator
1 parent b44d2fd commit aca87de

2 files changed

Lines changed: 214 additions & 0 deletions

File tree

src/flag_gems/runtime/backend/_mthreads/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from torch_musa import current_device, get_device_capability
1616

17+
from .adaptive_max_pool3d_backward import adaptive_max_pool3d_backward
1718
from .all import all, all_dim, all_dims
1819
from .amax import amax
1920
from .any import any, any_dim, any_dims
@@ -95,6 +96,7 @@
9596
from .zeros_like import zeros_like
9697

9798
__all__ = [
99+
"adaptive_max_pool3d_backward",
98100
"amax",
99101
"all",
100102
"all_dim",
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Copyright 2026 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+
import logging
16+
17+
import torch
18+
import triton
19+
import triton.language as tl
20+
21+
from flag_gems.ops.adaptive_max_pool3d_backward import (
22+
adaptive_max_pool3d_backward as default_adaptive_max_pool3d_backward,
23+
)
24+
from flag_gems.runtime import torch_device_fn
25+
from flag_gems.utils import libentry
26+
27+
logger = logging.getLogger(
28+
f"flag_gems.runtime.backend._mthreads.ops.{__name__.split('.')[-1]}"
29+
)
30+
31+
_SUPPORTED_DTYPES = {torch.float16, torch.bfloat16, torch.float32}
32+
33+
34+
@libentry()
35+
@triton.jit
36+
def _zero_fill_kernel(out_ptr, n_in, BLOCK: tl.constexpr):
37+
pid = tl.program_id(0)
38+
offs = pid * BLOCK + tl.arange(0, BLOCK)
39+
mask = offs < n_in
40+
tl.store(out_ptr + offs, tl.zeros((BLOCK,), dtype=tl.float32), mask=mask)
41+
42+
43+
@libentry()
44+
@triton.jit
45+
def _scatter_kernel(
46+
grad_ptr,
47+
idx_ptr,
48+
out_ptr,
49+
n_out,
50+
plane_in,
51+
plane_out,
52+
BLOCK: tl.constexpr,
53+
):
54+
pid = tl.program_id(0)
55+
offs = pid * BLOCK + tl.arange(0, BLOCK)
56+
mask = offs < n_out
57+
g = tl.load(grad_ptr + offs, mask=mask, other=0.0)
58+
idx = tl.load(idx_ptr + offs, mask=mask, other=0)
59+
target = (offs // plane_out) * plane_in + idx
60+
# accumulate: overlapping adaptive windows can map two outputs to one input
61+
tl.atomic_add(out_ptr + target, g, mask=mask)
62+
63+
64+
@libentry()
65+
@triton.jit
66+
def _gather_kernel(
67+
grad_ptr,
68+
idx_ptr,
69+
out_ptr,
70+
D_IN: tl.constexpr,
71+
PLANE_IN: tl.constexpr,
72+
PLANE_OUT: tl.constexpr,
73+
H_IN: tl.constexpr,
74+
W_IN: tl.constexpr,
75+
H_OUT: tl.constexpr,
76+
W_OUT: tl.constexpr,
77+
KD: tl.constexpr,
78+
KH: tl.constexpr,
79+
KW: tl.constexpr,
80+
BLOCK: tl.constexpr,
81+
FULL: tl.constexpr,
82+
):
83+
# Divisible case (all axes integral): adaptive windows tile the input
84+
# disjointly, so each input position belongs to exactly one window o(x)
85+
# and is a scatter target iff idx[o(x)] == x. 3D grid (plane, z, hw-block):
86+
# od is a scalar (z//KD) and per-lane work is only the (y,w) decomposition,
87+
# so the integer math chain is short. Single launch, no zero pass, no
88+
# atomics, stores fully coalesced.
89+
plane = tl.program_id(0)
90+
z = tl.program_id(1)
91+
pid = tl.program_id(2)
92+
od = z // KD
93+
hw = pid * BLOCK + tl.arange(0, BLOCK)
94+
if FULL:
95+
y = hw // W_IN
96+
w = hw - y * W_IN
97+
oh = y // KH
98+
ow = w // KW
99+
o_local = od * (H_OUT * W_OUT) + oh * W_OUT + ow
100+
o_flat = plane * PLANE_OUT + o_local
101+
idx0 = tl.load(idx_ptr + o_flat)
102+
g0 = tl.load(grad_ptr + o_flat)
103+
local = z * (H_IN * W_IN) + hw
104+
hit = idx0 == local
105+
val = tl.where(hit, g0, tl.zeros((BLOCK,), dtype=g0.dtype))
106+
tl.store(out_ptr + plane * PLANE_IN + local, val)
107+
else:
108+
mask = hw < H_IN * W_IN
109+
y = hw // W_IN
110+
w = hw - y * W_IN
111+
oh = y // KH
112+
ow = w // KW
113+
o_local = od * (H_OUT * W_OUT) + oh * W_OUT + ow
114+
o_flat = plane * PLANE_OUT + o_local
115+
idx0 = tl.load(idx_ptr + o_flat, mask=mask, other=0)
116+
g0 = tl.load(grad_ptr + o_flat, mask=mask, other=0.0)
117+
local = z * (H_IN * W_IN) + hw
118+
hit = idx0 == local
119+
val = tl.where(hit, g0, tl.zeros((BLOCK,), dtype=g0.dtype))
120+
tl.store(out_ptr + plane * PLANE_IN + local, val, mask=mask)
121+
122+
123+
def _use_triton_kernel(grad_output, self_input, indices) -> bool:
124+
if (
125+
not isinstance(grad_output, torch.Tensor)
126+
or not isinstance(self_input, torch.Tensor)
127+
or not isinstance(indices, torch.Tensor)
128+
):
129+
return False
130+
if grad_output.device.type != "musa" or grad_output.dtype not in _SUPPORTED_DTYPES:
131+
return False
132+
if (
133+
not grad_output.is_contiguous()
134+
or not self_input.is_contiguous()
135+
or not indices.is_contiguous()
136+
):
137+
return False
138+
if grad_output.numel() == 0 or self_input.numel() == 0:
139+
return False
140+
return True
141+
142+
143+
def adaptive_max_pool3d_backward(grad_output, self_input, indices):
144+
logger.debug("GEMS_MTHREADS ADAPTIVE_MAX_POOL3D_BACKWARD")
145+
if not _use_triton_kernel(grad_output, self_input, indices):
146+
return default_adaptive_max_pool3d_backward(grad_output, self_input, indices)
147+
out = torch.empty_like(self_input)
148+
n_in = self_input.numel()
149+
n_out = grad_output.numel()
150+
ds, hs, ws = (
151+
self_input.shape[-3],
152+
self_input.shape[-2],
153+
self_input.shape[-1],
154+
)
155+
do_, ho_, wo_ = (
156+
grad_output.shape[-3],
157+
grad_output.shape[-2],
158+
grad_output.shape[-1],
159+
)
160+
plane_in = ds * hs * ws
161+
plane_out = do_ * ho_ * wo_
162+
divisible = (ds % do_ == 0) and (hs % ho_ == 0) and (ws % wo_ == 0)
163+
with torch_device_fn.device(grad_output.device):
164+
if divisible:
165+
hw = hs * ws
166+
n_planes = grad_output.numel() // plane_out
167+
if hw >= 512:
168+
BLOCK, W = 128, 4
169+
elif hw >= 128:
170+
BLOCK, W = (256, 8) if hw % 256 == 0 else (128, 4)
171+
else:
172+
BLOCK, W = 64, 2
173+
full = hw % BLOCK == 0
174+
_gather_kernel[
175+
(n_planes, ds, hw // BLOCK if full else triton.cdiv(hw, BLOCK))
176+
](
177+
grad_output,
178+
indices,
179+
out,
180+
D_IN=ds,
181+
PLANE_IN=plane_in,
182+
PLANE_OUT=plane_out,
183+
H_IN=hs,
184+
W_IN=ws,
185+
H_OUT=ho_,
186+
W_OUT=wo_,
187+
KD=ds // do_,
188+
KH=hs // ho_,
189+
KW=ws // wo_,
190+
BLOCK=BLOCK,
191+
FULL=full,
192+
num_warps=W,
193+
)
194+
else:
195+
BLOCK = 1024
196+
_zero_fill_kernel[(triton.cdiv(n_in, BLOCK),)](
197+
out, n_in, BLOCK=BLOCK, num_warps=4
198+
)
199+
_scatter_kernel[(triton.cdiv(n_out, BLOCK),)](
200+
grad_output,
201+
indices,
202+
out,
203+
n_out,
204+
plane_in,
205+
plane_out,
206+
BLOCK=BLOCK,
207+
num_warps=4,
208+
)
209+
return out
210+
211+
212+
__all__ = ["adaptive_max_pool3d_backward"]

0 commit comments

Comments
 (0)