Skip to content

Commit 445b77e

Browse files
committed
[KernelGen][MThreads] Add diagonal_scatter Moore Threads specialized operator
1 parent b44d2fd commit 445b77e

2 files changed

Lines changed: 205 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
@@ -32,6 +32,7 @@
3232
true_divide_,
3333
true_divide_out,
3434
)
35+
from .diagonal_scatter import diagonal_scatter
3536
from .dropout import dropout, dropout_backward
3637
from .erfinv import erfinv
3738
from .erfinv_ import erfinv_
@@ -111,6 +112,7 @@
111112
"celu",
112113
# "celu_",
113114
"conv2d",
115+
"diagonal_scatter",
114116
"dropout",
115117
"dropout_backward",
116118
"erfinv",
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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.diagonal_scatter import diagonal_scatter as default_diagonal_scatter
22+
from flag_gems.runtime import torch_device_fn
23+
from flag_gems.utils import libentry
24+
25+
logger = logging.getLogger(
26+
f'flag_gems.runtime.backend._mthreads.ops.{__name__.split(".")[-1]}'
27+
)
28+
29+
_SUPPORTED_DTYPES = {torch.float16, torch.bfloat16, torch.float32}
30+
31+
MAX_BATCH_DIMS = 4
32+
33+
34+
@libentry()
35+
@triton.jit
36+
def _diag_scatter_kernel(
37+
in_ptr,
38+
src_ptr,
39+
out_ptr,
40+
offset: tl.constexpr,
41+
off_min: tl.constexpr,
42+
n1: tl.constexpr,
43+
n2: tl.constexpr,
44+
s1: tl.constexpr,
45+
s2: tl.constexpr,
46+
diag_size: tl.constexpr,
47+
br0: tl.constexpr,
48+
br1: tl.constexpr,
49+
br2: tl.constexpr,
50+
br3: tl.constexpr,
51+
bs0: tl.constexpr,
52+
bs1: tl.constexpr,
53+
bs2: tl.constexpr,
54+
bs3: tl.constexpr,
55+
n_i_tiles: tl.constexpr,
56+
BI: tl.constexpr,
57+
BJ: tl.constexpr,
58+
):
59+
pid0 = tl.program_id(0)
60+
pid1 = tl.program_id(1)
61+
62+
# i-tiles vary fastest so consecutive programs touch adjacent memory.
63+
i_tile = pid0 % n_i_tiles
64+
batch_flat = pid0 // n_i_tiles
65+
66+
# Decompose batch_flat (mixed-radix counter over the non-diagonal dims in
67+
# ascending dim order) into per-dim digits, innermost batch dim first, and
68+
# accumulate the corresponding flat base offset. Unused slots use radix=1,
69+
# stride=0, which makes them no-ops. All divisors are constexpr so the
70+
# div/mod lower to fast magic-number sequences.
71+
bf = batch_flat
72+
base = 0
73+
dig = bf % br0
74+
base += dig * bs0
75+
bf = bf // br0
76+
dig = bf % br1
77+
base += dig * bs1
78+
bf = bf // br1
79+
dig = bf % br2
80+
base += dig * bs2
81+
bf = bf // br2
82+
dig = bf % br3
83+
base += dig * bs3
84+
85+
i = i_tile * BI + tl.arange(0, BI)
86+
j = pid1 * BJ + tl.arange(0, BJ)
87+
mi = i < n1
88+
mj = j < n2
89+
90+
d = i + off_min
91+
src_idx = batch_flat * diag_size + d
92+
src_val = tl.load(
93+
src_ptr + src_idx, mask=mi & (d >= 0) & (d < diag_size), other=0.0
94+
)
95+
96+
on_diag = (
97+
((j[None, :] - i[:, None]) == offset)
98+
& mj[None, :]
99+
& mi[:, None]
100+
& (d[:, None] >= 0)
101+
& (d[:, None] < diag_size)
102+
)
103+
f = base + i[:, None] * s1 + j[None, :] * s2
104+
m = mi[:, None] & mj[None, :]
105+
in_val = tl.load(in_ptr + f, mask=m, other=0.0)
106+
out_val = tl.where(on_diag, src_val[:, None], in_val)
107+
tl.store(out_ptr + f, out_val, mask=m)
108+
109+
110+
def _use_triton_kernel(input, src, offset, dim1, dim2) -> bool:
111+
if not isinstance(input, torch.Tensor) or not isinstance(src, torch.Tensor):
112+
return False
113+
if input.device.type != "musa" or input.dtype not in _SUPPORTED_DTYPES:
114+
return False
115+
if not input.is_contiguous() or input.numel() == 0:
116+
return False
117+
ndim = input.ndim
118+
d1 = dim1 % ndim
119+
d2 = dim2 % ndim
120+
batch_dims = [d for d in range(ndim) if d != d1 and d != d2]
121+
if len(batch_dims) > MAX_BATCH_DIMS:
122+
return False
123+
return True
124+
125+
126+
def diagonal_scatter(input, src, offset=0, dim1=0, dim2=1):
127+
logger.debug("GEMS_MTHREADS DIAGONAL_SCATTER")
128+
if not _use_triton_kernel(input, src, offset, dim1, dim2):
129+
return default_diagonal_scatter(input, src, offset=offset, dim1=dim1, dim2=dim2)
130+
131+
ndim = input.ndim
132+
d1 = dim1 % ndim
133+
d2 = dim2 % ndim
134+
135+
shape = input.shape
136+
strides = input.stride()
137+
n1 = shape[d1]
138+
n2 = shape[d2]
139+
s1 = strides[d1]
140+
s2 = strides[d2]
141+
142+
batch_dims = [d for d in range(ndim) if d != d1 and d != d2]
143+
if len(batch_dims) > MAX_BATCH_DIMS:
144+
raise ValueError("tensor too high-dimensional for this kernel")
145+
146+
batch_count = 1
147+
for d in batch_dims:
148+
batch_count *= shape[d]
149+
150+
off_min = min(offset, 0)
151+
off_max = max(offset, 0)
152+
diag_size = max(0, min(n1 + off_min, n2 - off_max))
153+
154+
rad = [1] * MAX_BATCH_DIMS
155+
bst = [0] * MAX_BATCH_DIMS
156+
for k, d in enumerate(reversed(batch_dims)):
157+
rad[k] = shape[d]
158+
bst[k] = strides[d]
159+
160+
# Tile the (dim1, dim2) plane: BJ along dim2, BI along dim1, targeting
161+
# ~1024 elements per program block and ~4 elements per thread. Rows of
162+
# width n2=128 (BJ==128) uniquely prefer 4 warps (8 elems/thread) on the
163+
# MTT S5000; all other widths run 8 warps (4 elems/thread).
164+
BJ = min(256, 1 << (n2 - 1).bit_length()) if n2 > 0 else 256
165+
BI = min(1 << (n1 - 1).bit_length(), max(1, 1024 // BJ)) if n1 > 0 else 1
166+
n_i_tiles = (n1 + BI - 1) // BI
167+
n_j_tiles = (n2 + BJ - 1) // BJ
168+
grid = (batch_count * n_i_tiles, n_j_tiles)
169+
if BJ == 128:
170+
num_warps = max(1, min(8, (BI * BJ) // 256))
171+
else:
172+
num_warps = max(1, min(8, (BI * BJ) // 128))
173+
174+
with torch_device_fn.device(input.device):
175+
output = torch.empty_like(input)
176+
_diag_scatter_kernel[grid](
177+
input,
178+
src,
179+
output,
180+
offset,
181+
off_min,
182+
n1,
183+
n2,
184+
s1,
185+
s2,
186+
diag_size,
187+
rad[0],
188+
rad[1],
189+
rad[2],
190+
rad[3],
191+
bst[0],
192+
bst[1],
193+
bst[2],
194+
bst[3],
195+
n_i_tiles,
196+
BI,
197+
BJ,
198+
num_warps=num_warps,
199+
)
200+
return output
201+
202+
203+
__all__ = ["diagonal_scatter"]

0 commit comments

Comments
 (0)