Skip to content

Commit e591fec

Browse files
committed
[KernelGen][MThreads] Add amp_foreach_non_finite_check_and_unscale_ Moore Threads specialized operator
1 parent 4350421 commit e591fec

2 files changed

Lines changed: 228 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
@@ -16,6 +16,9 @@
1616

1717
from .all import all, all_dim, all_dims
1818
from .amax import amax
19+
from .amp_foreach_non_finite_check_and_unscale_ import (
20+
_amp_foreach_non_finite_check_and_unscale_,
21+
)
1922
from .any import any, any_dim, any_dims
2023
from .arange import arange, arange_start
2124
from .argmin import argmin
@@ -73,6 +76,7 @@
7376
from .zeros_like import zeros_like
7477

7578
__all__ = [
79+
"_amp_foreach_non_finite_check_and_unscale_",
7680
"amax",
7781
"all",
7882
"all_dim",
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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._amp_foreach_non_finite_check_and_unscale_ import (
22+
_amp_foreach_non_finite_check_and_unscale_ as default__amp_foreach_non_finite_check_and_unscale_,
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 _amp_unscale_check_kernel(
37+
x_ptr,
38+
numel,
39+
base,
40+
inv_scale_ptr,
41+
found_inf_ptr,
42+
BLOCK: tl.constexpr,
43+
EVEN: tl.constexpr,
44+
):
45+
pid = tl.program_id(0)
46+
offs = base + pid * BLOCK + tl.arange(0, BLOCK)
47+
inv = tl.load(inv_scale_ptr)
48+
if EVEN:
49+
# Unmasked bulk path: full blocks, enables vectorized load/store codegen.
50+
x = tl.load(x_ptr + offs)
51+
y = x.to(tl.float32)
52+
bits = y.to(tl.int32, bitcast=True)
53+
non_finite = (bits & 0x7F800000) == 0x7F800000
54+
found = tl.max(non_finite.to(tl.int32), axis=0) > 0
55+
if found:
56+
tl.atomic_xchg(found_inf_ptr, 1.0)
57+
tl.store(x_ptr + offs, (y * inv).to(x.dtype))
58+
else:
59+
# Masked tail path: only the last partial block of a tensor.
60+
mask = offs < numel
61+
x = tl.load(x_ptr + offs, mask=mask, other=0.0)
62+
y = x.to(tl.float32)
63+
bits = y.to(tl.int32, bitcast=True)
64+
non_finite = (bits & 0x7F800000) == 0x7F800000
65+
found = tl.max(non_finite.to(tl.int32), axis=0) > 0
66+
if found:
67+
tl.atomic_xchg(found_inf_ptr, 1.0)
68+
tl.store(x_ptr + offs, (y * inv).to(x.dtype), mask=mask)
69+
70+
71+
@libentry()
72+
@triton.jit
73+
def _amp_persistent_kernel(
74+
p0,
75+
p1,
76+
s1,
77+
total_blocks,
78+
inv_scale_ptr,
79+
found_inf_ptr,
80+
BLOCK: tl.constexpr,
81+
NUM_TENSORS: tl.constexpr,
82+
FP16: tl.constexpr,
83+
):
84+
# Persistent grid: each program strides over blocks and accumulates the
85+
# per-element non-finite flags in registers, so the expensive cross-warp
86+
# reduce runs once per program instead of once per block.
87+
pid = tl.program_id(0)
88+
ar = tl.arange(0, BLOCK)
89+
acc = tl.zeros([BLOCK], tl.int32)
90+
inv = tl.load(inv_scale_ptr)
91+
for blk in range(pid, total_blocks, tl.num_programs(0)):
92+
if NUM_TENSORS == 2:
93+
if blk >= s1:
94+
ptr = p1
95+
offs = (blk - s1) * BLOCK + ar
96+
else:
97+
ptr = p0
98+
offs = blk * BLOCK + ar
99+
else:
100+
ptr = p0
101+
offs = blk * BLOCK + ar
102+
x = tl.load(ptr + offs)
103+
if FP16:
104+
# All-ones fp16 exponent (0x1F) marks inf or NaN.
105+
bits = x.to(tl.int16, bitcast=True).to(tl.int32)
106+
non_finite = (bits & 0x7C00) == 0x7C00
107+
out = x * inv.to(tl.float16)
108+
else:
109+
y = x.to(tl.float32)
110+
bits = y.to(tl.int32, bitcast=True)
111+
non_finite = (bits & 0x7F800000) == 0x7F800000
112+
out = (y * inv).to(x.dtype)
113+
acc |= non_finite.to(tl.int32)
114+
tl.store(ptr + offs, out)
115+
found = tl.max(acc, axis=0) > 0
116+
if found:
117+
tl.atomic_xchg(found_inf_ptr, 1.0)
118+
119+
120+
_BLOCK = 4096
121+
_NUM_WARPS = 4
122+
_GRID = 256
123+
124+
125+
def _use_triton_kernel(tensors, found_inf, inv_scale) -> bool:
126+
if not isinstance(tensors, (list, tuple)) or len(tensors) == 0:
127+
return False
128+
if not isinstance(found_inf, torch.Tensor) or not isinstance(
129+
inv_scale, torch.Tensor
130+
):
131+
return False
132+
for t in tensors:
133+
if not isinstance(t, torch.Tensor):
134+
return False
135+
if t.device.type != "musa" or t.dtype not in _SUPPORTED_DTYPES:
136+
return False
137+
if not t.is_contiguous():
138+
return False
139+
return True
140+
141+
142+
def _amp_foreach_non_finite_check_and_unscale_(tensors, found_inf, inv_scale):
143+
logger.debug("GEMS_MTHREADS _AMP_FOREACH_NON_FINITE_CHECK_AND_UNSCALE_")
144+
if not _use_triton_kernel(tensors, found_inf, inv_scale):
145+
return default__amp_foreach_non_finite_check_and_unscale_(
146+
tensors, found_inf, inv_scale
147+
)
148+
n = len(tensors)
149+
if n == 0:
150+
return None
151+
fp16 = tensors[0].dtype == torch.float16
152+
with torch_device_fn.device(tensors[0].device):
153+
if n == 1:
154+
numel = tensors[0].numel()
155+
if numel % _BLOCK == 0 and numel > 0:
156+
total = numel // _BLOCK
157+
grid = min(total, _GRID)
158+
_amp_persistent_kernel[(grid,)](
159+
tensors[0],
160+
tensors[0],
161+
total,
162+
total,
163+
inv_scale,
164+
found_inf,
165+
BLOCK=_BLOCK,
166+
NUM_TENSORS=1,
167+
FP16=fp16,
168+
num_warps=_NUM_WARPS,
169+
)
170+
return None
171+
elif n == 2:
172+
n0 = tensors[0].numel()
173+
n1 = tensors[1].numel()
174+
if n0 % _BLOCK == 0 and n1 % _BLOCK == 0:
175+
s1 = n0 // _BLOCK
176+
total = s1 + n1 // _BLOCK
177+
grid = min(total, _GRID)
178+
_amp_persistent_kernel[(grid,)](
179+
tensors[0],
180+
tensors[1],
181+
s1,
182+
total,
183+
inv_scale,
184+
found_inf,
185+
BLOCK=_BLOCK,
186+
NUM_TENSORS=2,
187+
FP16=fp16,
188+
num_warps=_NUM_WARPS,
189+
)
190+
return None
191+
# Generic per-tensor path (masked tails or n > 2).
192+
for t in tensors:
193+
numel = t.numel()
194+
if numel == 0:
195+
continue
196+
full = numel // _BLOCK
197+
if full > 0:
198+
_amp_unscale_check_kernel[(full,)](
199+
t,
200+
numel,
201+
0,
202+
inv_scale,
203+
found_inf,
204+
BLOCK=_BLOCK,
205+
EVEN=True,
206+
num_warps=_NUM_WARPS,
207+
)
208+
tail = numel % _BLOCK
209+
if tail:
210+
base = full * _BLOCK
211+
_amp_unscale_check_kernel[(1,)](
212+
t,
213+
numel,
214+
base,
215+
inv_scale,
216+
found_inf,
217+
BLOCK=_BLOCK,
218+
EVEN=False,
219+
num_warps=_NUM_WARPS,
220+
)
221+
return None
222+
223+
224+
__all__ = ["_amp_foreach_non_finite_check_and_unscale_"]

0 commit comments

Comments
 (0)