Skip to content

Commit 9726e77

Browse files
committed
[KernelGen][MThreads] Add clip_ Moore Threads specialized operator
1 parent b44d2fd commit 9726e77

2 files changed

Lines changed: 101 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
@@ -22,6 +22,7 @@
2222
from .batch_norm import batch_norm, batch_norm_backward
2323
from .bucketize import bucketize
2424
from .celu import celu
25+
from .clip_ import clip_
2526
from .conv2d import conv2d
2627
from .div import (
2728
div_mode,
@@ -110,6 +111,7 @@
110111
"bucketize",
111112
"celu",
112113
# "celu_",
114+
"clip_",
113115
"conv2d",
114116
"dropout",
115117
"dropout_backward",
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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.clip import clip_ as default_clip_
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+
32+
@libentry()
33+
@triton.jit
34+
def clip_kernel(
35+
x_ptr,
36+
mini,
37+
maxi,
38+
n_elements,
39+
HAS_MIN: tl.constexpr,
40+
HAS_MAX: tl.constexpr,
41+
BLOCK: tl.constexpr,
42+
GRID: tl.constexpr,
43+
):
44+
pid = tl.program_id(0)
45+
step = BLOCK * GRID
46+
for start in range(pid * BLOCK, n_elements, step):
47+
offsets = start + tl.arange(0, BLOCK)
48+
mask = offsets < n_elements
49+
x = tl.load(x_ptr + offsets, mask=mask, other=0.0)
50+
if HAS_MAX:
51+
x = tl.minimum(x, maxi)
52+
if HAS_MIN:
53+
x = tl.maximum(x, mini)
54+
tl.store(x_ptr + offsets, x, mask=mask)
55+
56+
57+
def _use_triton_kernel(x: torch.Tensor, mini, maxi) -> bool:
58+
if not isinstance(x, torch.Tensor):
59+
return False
60+
if x.device.type != "musa" or x.dtype not in _SUPPORTED_DTYPES:
61+
return False
62+
if not x.is_contiguous() or x.numel() == 0:
63+
return False
64+
# Only scalar min/max are specialized here; tensor bounds fall back to generic.
65+
for v in (mini, maxi):
66+
if v is not None and not isinstance(v, (int, float)):
67+
return False
68+
return True
69+
70+
71+
def clip_(x: torch.Tensor, mini=None, maxi=None):
72+
logger.debug("GEMS_MTHREADS CLIP_")
73+
if not _use_triton_kernel(x, mini, maxi):
74+
return default_clip_(x, mini, maxi)
75+
76+
has_min = mini is not None
77+
has_max = maxi is not None
78+
mini_v = float(mini) if has_min else 0.0
79+
maxi_v = float(maxi) if has_max else 0.0
80+
n = x.numel()
81+
# Persistent grid-stride loop: BLOCK=1024, GRID=60*8 sized for the S5000
82+
# (60 SMs). Hardcoded (not autotuned) because this is an inplace kernel —
83+
# autotune would rerun on the same buffer and corrupt the data.
84+
BLOCK = 1024
85+
GRID = 60 * 8
86+
grid = (min(triton.cdiv(n, BLOCK), GRID),)
87+
with torch_device_fn.device(x.device):
88+
clip_kernel[grid](
89+
x,
90+
mini_v,
91+
maxi_v,
92+
n,
93+
HAS_MIN=has_min,
94+
HAS_MAX=has_max,
95+
BLOCK=BLOCK,
96+
GRID=GRID,
97+
num_warps=4,
98+
)
99+
return x

0 commit comments

Comments
 (0)