Skip to content

Commit f92b65d

Browse files
[KernelGen][Nvidia] Move arctanh operator to ops (#4818)
* [KernelGen][Nvidia] Move arctanh operator to ops * Add arctanh_out id in operators.yaml, accuracy test and benchmark --------- Co-authored-by: w1120029931-bit <w1120029931@gmail.com>
1 parent 3692e08 commit f92b65d

6 files changed

Lines changed: 159 additions & 0 deletions

File tree

benchmark/test_arctanh.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,26 @@
1313
# limitations under the License.
1414

1515
import pytest
16+
import torch
1617

1718
import flag_gems
1819

1920
from . import base, consts
2021

2122

23+
@pytest.mark.arctanh
24+
@pytest.mark.skipif(
25+
flag_gems.vendor_name == "tsingmicro", reason="Issue #4131: not working"
26+
)
27+
def test_arctanh():
28+
bench = base.UnaryPointwiseBenchmark(
29+
op_name="arctanh",
30+
torch_op=torch.arctanh,
31+
dtypes=consts.FLOAT_DTYPES,
32+
)
33+
bench.run()
34+
35+
2236
@pytest.mark.arctanh_
2337
@pytest.mark.skipif(
2438
flag_gems.vendor_name == "tsingmicro", reason="Issue #4131: not working"
@@ -31,3 +45,16 @@ def test_arctanh_inplace():
3145
is_inplace=True,
3246
)
3347
bench.run()
48+
49+
50+
@pytest.mark.arctanh_out
51+
@pytest.mark.skipif(
52+
flag_gems.vendor_name == "tsingmicro", reason="Issue #4131: not working"
53+
)
54+
def test_arctanh_out():
55+
bench = base.UnaryPointwiseOutBenchmark(
56+
op_name="arctanh_out",
57+
torch_op=torch.arctanh,
58+
dtypes=consts.FLOAT_DTYPES,
59+
)
60+
bench.run()

conf/operators.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,18 @@ ops:
886886
- Math
887887
stages:
888888
- alpha: '5.4'
889+
- id: arctanh
890+
description: |
891+
Computes the element-wise inverse hyperbolic tangent of a given input tensor.
892+
for:
893+
- arctanh
894+
labels:
895+
- aten
896+
- KernelGen
897+
kind:
898+
- Math
899+
stages:
900+
- alpha: '5.4'
889901
- id: arctanh_
890902
description: |
891903
Computes the element-wise inverse hyperbolic tangent of a given input tensor.
@@ -900,6 +912,18 @@ ops:
900912
stages:
901913
- beta: '5.0'
902914
- stable: '5.4'
915+
- id: arctanh_out
916+
description: |
917+
A variant of `arctanh` that assigns the output to the provided `out` parameter.
918+
for:
919+
- arctanh.out
920+
labels:
921+
- aten
922+
- KernelGen
923+
kind:
924+
- Math
925+
stages:
926+
- alpha: '5.4'
903927
- id: argmax
904928
description: Returns the indices of the maximum value of all elements in the `input` tensor.
905929
for:

src/flag_gems/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ def torch_ge(v):
281281
("arctan2", arctan2),
282282
("arctan2_", arctan2_),
283283
("arctan_", arctan_),
284+
("arctanh", arctanh),
285+
("arctanh.out", arctanh_out),
284286
("arctanh_", arctanh_),
285287
("argmax", argmax),
286288
("argmin", argmin),

src/flag_gems/ops/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
from flag_gems.ops.arcsinh_ import arcsinh_
139139
from flag_gems.ops.arctan2 import arctan2, arctan2_
140140
from flag_gems.ops.arctan_ import arctan, arctan_
141+
from flag_gems.ops.arctanh import arctanh, arctanh_out
141142
from flag_gems.ops.arctanh_ import arctanh_
142143
from flag_gems.ops.argmax import argmax
143144
from flag_gems.ops.argmin import argmin
@@ -921,7 +922,9 @@
921922
"arctan2",
922923
"arctan2_",
923924
"arctan_",
925+
"arctanh",
924926
"arctanh_",
927+
"arctanh_out",
925928
"argmax",
926929
"argmin",
927930
"argsort",

src/flag_gems/ops/arctanh.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 torch
16+
import triton
17+
import triton.language as tl
18+
19+
20+
@triton.jit
21+
def arctanh_kernel(x_ptr, out_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
22+
pid = tl.program_id(axis=0)
23+
block_start = pid * BLOCK_SIZE
24+
offsets = block_start + tl.arange(0, BLOCK_SIZE)
25+
mask = offsets < n_elements
26+
27+
x = tl.load(x_ptr + offsets, mask=mask, other=0)
28+
x_f32 = x.to(tl.float32)
29+
30+
one = 1.0
31+
# atanh(x) = 0.5 * (log(1 + x) - log(1 - x))
32+
y_f32 = 0.5 * (tl.log(one + x_f32) - tl.log(one - x_f32))
33+
y = y_f32.to(x.dtype)
34+
35+
tl.store(out_ptr + offsets, y, mask=mask)
36+
37+
38+
def _launch_arctanh(x: torch.Tensor, out: torch.Tensor):
39+
assert x.is_cuda and out.is_cuda, "Input and output must be CUDA tensors"
40+
assert x.shape == out.shape, "Input and output shapes must match"
41+
assert out.dtype == x.dtype, "Output dtype must match input dtype"
42+
assert x.dtype in (
43+
torch.float16,
44+
torch.bfloat16,
45+
torch.float32,
46+
), "Supported dtypes: float16, bfloat16, float32"
47+
48+
x_contig = x.contiguous()
49+
out_contig = out if out.is_contiguous() else torch.empty_like(out)
50+
51+
n_elements = x_contig.numel()
52+
if n_elements == 0:
53+
if out_contig is not out:
54+
out.copy_(out_contig)
55+
return out
56+
57+
grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),)
58+
arctanh_kernel[grid](x_contig, out_contig, n_elements, BLOCK_SIZE=1024)
59+
60+
if out_contig is not out:
61+
out.copy_(out_contig)
62+
return out
63+
64+
65+
def arctanh(x: torch.Tensor):
66+
out = torch.empty_like(x)
67+
_launch_arctanh(x, out)
68+
return out
69+
70+
71+
def arctanh_out(x: torch.Tensor, out: torch.Tensor):
72+
_launch_arctanh(x, out)
73+
return out

tests/test_arctanh.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@
2020
from . import accuracy_utils as utils
2121

2222

23+
@pytest.mark.arctanh
24+
@pytest.mark.parametrize("shape", utils.POINTWISE_SHAPES)
25+
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES)
26+
def test_arctanh(shape, dtype):
27+
inp = torch.empty(shape, dtype=dtype, device=flag_gems.device).uniform_(-0.99, 0.99)
28+
ref_inp = utils.to_reference(inp)
29+
30+
ref_out = torch.arctanh(ref_inp)
31+
with flag_gems.use_gems():
32+
res_out = torch.arctanh(inp)
33+
34+
utils.gems_assert_close(res_out, ref_out, dtype)
35+
36+
2337
@pytest.mark.arctanh_
2438
@pytest.mark.parametrize("shape", utils.POINTWISE_SHAPES)
2539
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES)
@@ -32,3 +46,19 @@ def test_arctanh_(shape, dtype):
3246
res_out = inp.arctanh_()
3347

3448
utils.gems_assert_close(res_out, ref_out, dtype)
49+
50+
51+
@pytest.mark.arctanh_out
52+
@pytest.mark.parametrize("shape", utils.POINTWISE_SHAPES)
53+
@pytest.mark.parametrize("dtype", utils.FLOAT_DTYPES)
54+
def test_arctanh_out(shape, dtype):
55+
inp = torch.empty(shape, dtype=dtype, device=flag_gems.device).uniform_(-0.99, 0.99)
56+
out = torch.empty_like(inp)
57+
ref_inp = utils.to_reference(inp)
58+
ref_out = torch.empty_like(ref_inp)
59+
60+
torch.arctanh(ref_inp, out=ref_out)
61+
with flag_gems.use_gems():
62+
torch.arctanh(inp, out=out)
63+
64+
utils.gems_assert_close(out, ref_out, dtype)

0 commit comments

Comments
 (0)