Skip to content

Commit cfdc30e

Browse files
committed
[KernelGen][MThreads] Add softplus_backward Moore Threads specialized operator
1 parent 1cd434c commit cfdc30e

2 files changed

Lines changed: 149 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
@@ -55,6 +55,7 @@
5555
repeat_interleave_tensor,
5656
)
5757
from .resolve_conj import resolve_conj
58+
from .softplus_backward import softplus_backward
5859
from .sort import sort, sort_stable
5960
from .tile import tile
6061
from .unique import _unique2
@@ -117,6 +118,7 @@
117118
"repeat_interleave_self_tensor",
118119
"repeat_interleave_tensor",
119120
"resolve_conj",
121+
"softplus_backward",
120122
"sort",
121123
"sort_stable",
122124
"tile",
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
from typing import Tuple
18+
19+
import torch
20+
import triton
21+
import triton.language as tl
22+
23+
from flag_gems.ops.softplus import (
24+
softplus_backward as default_softplus_backward, # fallback
25+
)
26+
from flag_gems.runtime import torch_device_fn
27+
from flag_gems.utils import libentry, tl_extra_shim
28+
29+
logger = logging.getLogger(__name__)
30+
31+
_SUPPORTED_DTYPES = {torch.float16, torch.bfloat16, torch.float32}
32+
exp = tl_extra_shim.exp
33+
34+
35+
@libentry()
36+
@triton.autotune(
37+
configs=[
38+
triton.Config({"BLOCK_SIZE": 256, "VEC": 4}, num_warps=4, num_stages=1),
39+
triton.Config({"BLOCK_SIZE": 256, "VEC": 2}, num_warps=4, num_stages=1),
40+
triton.Config({"BLOCK_SIZE": 512, "VEC": 2}, num_warps=8, num_stages=1),
41+
triton.Config({"BLOCK_SIZE": 512, "VEC": 4}, num_warps=8, num_stages=1),
42+
triton.Config({"BLOCK_SIZE": 1024, "VEC": 1}, num_warps=4, num_stages=2),
43+
triton.Config({"BLOCK_SIZE": 1024, "VEC": 2}, num_warps=8, num_stages=2),
44+
triton.Config({"BLOCK_SIZE": 2048, "VEC": 1}, num_warps=8, num_stages=2),
45+
triton.Config({"BLOCK_SIZE": 4096, "VEC": 1}, num_warps=16, num_stages=2),
46+
],
47+
key=["n_elements", "dtype_size"],
48+
)
49+
@triton.jit(do_not_specialize=["beta", "threshold"])
50+
def softplus_backward_kernel(
51+
grad_ptr,
52+
x_ptr,
53+
out_ptr,
54+
n_elements,
55+
beta,
56+
threshold,
57+
dtype_size, # used for autotune key
58+
BLOCK_SIZE: tl.constexpr,
59+
VEC: tl.constexpr,
60+
):
61+
pid = tl.program_id(0)
62+
BLOCK_ELEMS: tl.constexpr = BLOCK_SIZE * VEC
63+
offsets = (pid * BLOCK_ELEMS + tl.arange(0, BLOCK_ELEMS)).to(tl.int64)
64+
mask = offsets < n_elements
65+
66+
dy = tl.load(grad_ptr + offsets, mask=mask, other=0.0)
67+
x = tl.load(x_ptr + offsets, mask=mask, other=0.0)
68+
69+
x_fp32 = x.to(tl.float32)
70+
z = x_fp32 * beta
71+
# d/dx softplus(x) = sigmoid(beta * x) when z <= threshold, else 1
72+
# sigmoid(z) = 1 / (1 + exp(-z))
73+
sig = 1.0 / (1.0 + exp(-z))
74+
dydx = tl.where(z > threshold, 1.0, sig)
75+
dx = (dy.to(tl.float32) * dydx).to(x.dtype)
76+
77+
tl.store(out_ptr + offsets, dx, mask=mask)
78+
79+
80+
def _coerce_scalar(value, name: str) -> Tuple[float, bool]:
81+
try:
82+
v = float(value) if not isinstance(value, torch.Tensor) else float(value.item())
83+
except Exception:
84+
return 0.0, False
85+
if not math.isfinite(v):
86+
return 0.0, False
87+
return v, True
88+
89+
90+
def _use_triton_kernel(
91+
grad_output: torch.Tensor, x: torch.Tensor, beta, threshold
92+
) -> Tuple[bool, float, float]:
93+
if not isinstance(grad_output, torch.Tensor) or not isinstance(x, torch.Tensor):
94+
return False, 0.0, 0.0
95+
if grad_output.device.type != "musa" or x.device.type != "musa":
96+
return False, 0.0, 0.0
97+
if grad_output.dtype != x.dtype or grad_output.dtype not in _SUPPORTED_DTYPES:
98+
return False, 0.0, 0.0
99+
if (
100+
grad_output.numel() != x.numel()
101+
or grad_output.numel() == 0
102+
or not grad_output.is_contiguous()
103+
or not x.is_contiguous()
104+
):
105+
return False, 0.0, 0.0
106+
beta_value, ok_beta = _coerce_scalar(beta, "beta")
107+
threshold_value, ok_thr = _coerce_scalar(threshold, "threshold")
108+
if not ok_beta or not ok_thr:
109+
return False, 0.0, 0.0
110+
return True, beta_value, threshold_value
111+
112+
113+
def _launch_softplus_backward(
114+
grad_output: torch.Tensor,
115+
x: torch.Tensor,
116+
out: torch.Tensor,
117+
beta: float,
118+
threshold: float,
119+
dtype_size: int,
120+
):
121+
grad_flat = grad_output.view(-1)
122+
x_flat = x.view(-1)
123+
out_flat = out.view(-1)
124+
n_elements = out_flat.numel()
125+
grid = lambda META: (triton.cdiv(n_elements, META["BLOCK_SIZE"] * META["VEC"]),)
126+
with torch_device_fn.device(out.device):
127+
softplus_backward_kernel[grid](
128+
grad_flat, x_flat, out_flat, n_elements, beta, threshold, dtype_size
129+
)
130+
return out
131+
132+
133+
def softplus_backward(grad_output, self, beta=1.0, threshold=20.0):
134+
logger.debug("GEMS_MTHREADS SOFTPLUS_BACKWARD")
135+
use_triton, beta_value, threshold_value = _use_triton_kernel(
136+
grad_output, self, beta, threshold
137+
)
138+
if not use_triton:
139+
return default_softplus_backward(
140+
grad_output, self, beta=beta, threshold=threshold
141+
)
142+
143+
out = torch.empty_like(self)
144+
dtype_size = self.element_size()
145+
return _launch_softplus_backward(
146+
grad_output, self, out, beta_value, threshold_value, dtype_size
147+
)

0 commit comments

Comments
 (0)