Skip to content

Commit e8d77e2

Browse files
Yukun-CuiDongxu-H
andauthored
[KernelGen][MThreads] Add norm Moore Threads specialized operator (#261)
Co-authored-by: Dongxu-H <dxhan@baai.ac.cn>
1 parent 1375a82 commit e8d77e2

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from .min import min, min_dim
4545
from .mode import mode
4646
from .nonzero_numpy import nonzero_numpy
47+
from .norm import norm, norm_scalar, norm_scalaropt_dim
4748
from .normal import normal_
4849
from .one_hot import one_hot
4950
from .ones import ones
@@ -115,6 +116,9 @@
115116
"min_dim",
116117
"mode",
117118
"nonzero_numpy",
119+
"norm",
120+
"norm_scalar",
121+
"norm_scalaropt_dim",
118122
"normal_",
119123
"one_hot",
120124
"ones",
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
import math
17+
18+
import torch
19+
import triton
20+
import triton.language as tl
21+
22+
from flag_gems.ops.norm import norm as default_norm
23+
from flag_gems.ops.norm import norm_scalar as default_norm_scalar
24+
from flag_gems.ops.norm import norm_scalaropt_dim as default_norm_scalaropt_dim
25+
from flag_gems.runtime import torch_device_fn
26+
from flag_gems.utils import libentry, tl_extra_shim
27+
from flag_gems.utils import triton_lang_extension as tle
28+
29+
pow = tl_extra_shim.pow
30+
logger = logging.getLogger(
31+
f'flag_gems.runtime.backend._mthreads.ops.{__name__.split(".")[-1]}'
32+
)
33+
34+
_SUPPORTED_DTYPES = {torch.float16, torch.bfloat16, torch.float32}
35+
36+
# Reduction identifiers. Wrapped as tl.constexpr so they can be referenced from
37+
# within @triton.jit kernels (plain module globals are not accessible there).
38+
# 0: L2 (sum of squares), 1: +inf (max abs), 2: -inf (min abs),
39+
# 3: L0 (count nonzero), 4: general Lp (sum of |x|^p).
40+
_RED_L2 = tl.constexpr(0)
41+
_RED_MAX = tl.constexpr(1)
42+
_RED_MIN = tl.constexpr(2)
43+
_RED_L0 = tl.constexpr(3)
44+
_RED_LP = tl.constexpr(4)
45+
46+
47+
@libentry()
48+
@triton.autotune(
49+
configs=[
50+
triton.Config({"BLOCK_SIZE": 1024}, num_warps=4, num_stages=1),
51+
triton.Config({"BLOCK_SIZE": 2048}, num_warps=8, num_stages=1),
52+
triton.Config({"BLOCK_SIZE": 4096}, num_warps=8, num_stages=1),
53+
triton.Config({"BLOCK_SIZE": 4096}, num_warps=16, num_stages=1),
54+
],
55+
key=["M"],
56+
)
57+
@triton.jit(do_not_specialize=["ord"])
58+
def norm_partial_kernel(
59+
X,
60+
Partial,
61+
M,
62+
ord,
63+
num_blocks,
64+
RED: tl.constexpr,
65+
BLOCK_SIZE: tl.constexpr,
66+
):
67+
# Grid-stride pass 1: each program folds many BLOCK_SIZE-wide tiles into a
68+
# single partial, so the grid stays bounded regardless of M (the generic
69+
# kernel launches ~sqrt(M) programs each doing one giant vector load, which
70+
# collapses occupancy on large tensors).
71+
pid = tle.program_id(0)
72+
if RED == _RED_MAX:
73+
acc = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
74+
elif RED == _RED_MIN:
75+
acc = tl.full([BLOCK_SIZE], float("inf"), dtype=tl.float32)
76+
else:
77+
acc = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
78+
79+
start = pid * BLOCK_SIZE
80+
stride = num_blocks * BLOCK_SIZE
81+
for off in range(start, M, stride):
82+
cols = off + tl.arange(0, BLOCK_SIZE)
83+
mask = cols < M
84+
if RED == _RED_MIN:
85+
a = tl.load(X + cols, mask=mask, other=float("inf")).to(tl.float32)
86+
acc = tl.minimum(tl.abs(a), acc)
87+
else:
88+
a = tl.load(X + cols, mask=mask, other=0.0).to(tl.float32)
89+
if RED == _RED_L2:
90+
acc += a * a
91+
elif RED == _RED_MAX:
92+
acc = tl.maximum(tl.abs(a), acc)
93+
elif RED == _RED_L0:
94+
acc += tl.where(a != 0, 1.0, 0.0)
95+
else: # _RED_LP
96+
acc += pow(tl.abs(a), ord)
97+
98+
if RED == _RED_MAX:
99+
val = tl.max(acc)
100+
elif RED == _RED_MIN:
101+
val = tl.min(acc)
102+
else:
103+
val = tl.sum(acc)
104+
tl.store(Partial + pid, val)
105+
106+
107+
@libentry()
108+
@triton.jit(do_not_specialize=["ord"])
109+
def norm_finalize_kernel(
110+
Partial,
111+
Out,
112+
num_blocks,
113+
ord,
114+
RED: tl.constexpr,
115+
BLOCK_MID: tl.constexpr,
116+
):
117+
offset = tl.arange(0, BLOCK_MID)
118+
mask = offset < num_blocks
119+
if RED == _RED_MIN:
120+
p = tl.load(Partial + offset, mask=mask, other=float("inf")).to(tl.float32)
121+
out = tl.min(p)
122+
elif RED == _RED_MAX:
123+
p = tl.load(Partial + offset, mask=mask, other=0.0).to(tl.float32)
124+
out = tl.max(p)
125+
else:
126+
p = tl.load(Partial + offset, mask=mask, other=0.0).to(tl.float32)
127+
s = tl.sum(p)
128+
if RED == _RED_L2:
129+
out = tl.sqrt(s)
130+
elif RED == _RED_L0:
131+
out = s
132+
else: # _RED_LP
133+
out = pow(tl.abs(s), 1.0 / ord)
134+
tl.store(Out, out)
135+
136+
137+
def _red_kind(p):
138+
if p == 2:
139+
return _RED_L2.value, 2.0
140+
if p == float("inf"):
141+
return _RED_MAX.value, 0.0
142+
if p == -float("inf"):
143+
return _RED_MIN.value, 0.0
144+
if p == 0:
145+
return _RED_L0.value, 0.0
146+
return _RED_LP.value, float(p)
147+
148+
149+
def _is_full_reduction(x, dim) -> bool:
150+
if dim is None:
151+
return True
152+
if isinstance(dim, (list, tuple)):
153+
if len(dim) == 0:
154+
return True
155+
axes = {d % x.ndim for d in dim}
156+
return len(axes) == x.ndim
157+
return False
158+
159+
160+
def _use_triton_kernel(x, p, dim) -> bool:
161+
if not isinstance(x, torch.Tensor):
162+
return False
163+
if x.device.type != "musa" or x.dtype not in _SUPPORTED_DTYPES:
164+
return False
165+
# Only the full-tensor reduction is specialized here; a partial per-dim
166+
# reduction defers to the generic implementation. torch expresses a full
167+
# reduction as dim=None, dim=[] (empty sequence), or a dim list covering
168+
# every axis (e.g. [0, 1] for a 2-D input) -- all handled as full reduction.
169+
if not _is_full_reduction(x, dim):
170+
return False
171+
if x.numel() == 0:
172+
return False
173+
if p is not None and not isinstance(p, (int, float)):
174+
return False
175+
if isinstance(p, float) and math.isnan(p):
176+
return False
177+
return True
178+
179+
180+
def norm(x, p=2, dim=None, keepdim=False):
181+
logger.debug("GEMS_MTHREADS NORM")
182+
if not _use_triton_kernel(x, p, dim):
183+
return default_norm(x, p=p, dim=dim, keepdim=keepdim)
184+
185+
dtype = x.dtype
186+
red, ord_val = _red_kind(p)
187+
188+
x = x.contiguous()
189+
M = x.numel()
190+
# Cap the grid so pass 1 stays occupancy-bound rather than launch-bound; a
191+
# few thousand programs saturate the device while keeping the pass-2 reduce
192+
# over the partials small.
193+
max_blocks = 4096
194+
x_flat = x.view(-1)
195+
out = torch.empty([1] * x.ndim, dtype=dtype, device=x.device)
196+
197+
with torch_device_fn.device(x.device):
198+
num_blocks = min(max_blocks, triton.cdiv(M, 1024))
199+
num_blocks = max(1, num_blocks)
200+
partial = torch.empty([num_blocks], dtype=torch.float32, device=x.device)
201+
grid = (num_blocks,)
202+
norm_partial_kernel[grid](x_flat, partial, M, ord_val, num_blocks, red)
203+
block_mid = triton.next_power_of_2(num_blocks)
204+
norm_finalize_kernel[(1,)](partial, out, num_blocks, ord_val, red, block_mid)
205+
206+
if not keepdim:
207+
out = out.reshape([])
208+
return out
209+
210+
211+
def norm_scalar(x, p=2):
212+
logger.debug("GEMS_MTHREADS NORM_SCALAR")
213+
if not _use_triton_kernel(x, p, None):
214+
return default_norm_scalar(x, p=p)
215+
return norm(x, p=p, dim=None, keepdim=False)
216+
217+
218+
def norm_scalaropt_dim(x, p, dim, keepdim=False):
219+
logger.debug("GEMS_MTHREADS NORM_SCALAR_OPT_DIM")
220+
# Only the full-tensor case is specialized; dim reductions defer to generic.
221+
if not _use_triton_kernel(x, p, dim):
222+
return default_norm_scalaropt_dim(x, p, dim, keepdim=keepdim)
223+
return norm(x, p=p, dim=None, keepdim=keepdim)

0 commit comments

Comments
 (0)