Skip to content

Commit c807332

Browse files
factnnclaude
andcommitted
feat: add nonzero_numpy operator implementation, tests and benchmark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 201171c commit c807332

5 files changed

Lines changed: 76 additions & 0 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import pytest
2+
import torch
3+
4+
from benchmark.attri_util import BOOL_DTYPES, FLOAT_DTYPES, INT_DTYPES
5+
from benchmark.performance_utils import GenericBenchmark, unary_input_fn
6+
7+
8+
@pytest.mark.nonzero_numpy
9+
def test_perf_nonzero_numpy():
10+
bench = GenericBenchmark(
11+
input_fn=unary_input_fn,
12+
op_name="nonzero_numpy",
13+
torch_op=torch.ops.aten.nonzero_numpy,
14+
dtypes=FLOAT_DTYPES + INT_DTYPES + BOOL_DTYPES,
15+
)
16+
bench.run()

src/flag_gems/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ def torch_ge(v):
328328
("nll_loss2d_backward", nll_loss2d_backward),
329329
("nll_loss2d_forward", nll_loss2d_forward),
330330
("nonzero", nonzero),
331+
("nonzero_numpy", nonzero_numpy),
331332
("normal.Tensor_float", normal_tensor_float),
332333
("normal.Tensor_Tensor", normal_tensor_tensor),
333334
("normal.float_Tensor", normal_float_tensor),

src/flag_gems/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@
207207
nll_loss_forward,
208208
)
209209
from flag_gems.ops.nonzero import nonzero
210+
from flag_gems.ops.nonzero_numpy import nonzero_numpy
210211
from flag_gems.ops.normal import (
211212
normal_,
212213
normal_float_tensor,
@@ -590,6 +591,7 @@
590591
"nll_loss_nd_forward",
591592
"nll_loss_nd_backward",
592593
"nonzero",
594+
"nonzero_numpy",
593595
"normal_float_tensor",
594596
"normal_tensor_float",
595597
"normal_tensor_tensor",

src/flag_gems/ops/nonzero_numpy.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import logging
2+
3+
from flag_gems.ops.nonzero import nonzero
4+
5+
logger = logging.getLogger(__name__)
6+
7+
8+
def nonzero_numpy(inp):
9+
"""
10+
Returns a tuple of 1D tensors, one for each dimension of the input,
11+
containing the indices of the non-zero elements in that dimension.
12+
13+
This is equivalent to torch.nonzero(...).T or numpy.nonzero() behavior.
14+
"""
15+
logger.debug("GEMS NONZERO_NUMPY")
16+
17+
# Use the existing nonzero implementation which returns shape [N, ndim]
18+
out = nonzero(inp, as_tuple=False)
19+
20+
# Unbind along dim=1 to get ndim tensors of shape [N]
21+
# Convert to list since aten::nonzero_numpy returns Tensor[]
22+
return list(out.unbind(dim=1))

tests/test_nonzero_numpy.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
import torch
3+
4+
import flag_gems
5+
6+
from .accuracy_utils import (
7+
FLOAT_DTYPES,
8+
INT_DTYPES,
9+
NONZERO_SHAPES,
10+
gems_assert_equal,
11+
to_reference,
12+
)
13+
14+
15+
@pytest.mark.nonzero_numpy
16+
@pytest.mark.parametrize("shape", NONZERO_SHAPES)
17+
@pytest.mark.parametrize("dtype", FLOAT_DTYPES + INT_DTYPES + [torch.bool])
18+
def test_accuracy_nonzero_numpy(shape, dtype):
19+
if dtype == torch.bool:
20+
inp = torch.randint(0, 2, shape, dtype=torch.int, device=flag_gems.device).to(
21+
dtype
22+
)
23+
elif dtype in INT_DTYPES:
24+
inp = torch.randint(-3, 3, shape, device=flag_gems.device).to(dtype)
25+
else:
26+
inp = torch.randn(shape, dtype=dtype, device=flag_gems.device)
27+
ref_inp = to_reference(inp, False)
28+
29+
ref_out = torch.ops.aten.nonzero_numpy(ref_inp)
30+
with flag_gems.use_gems():
31+
res_out = torch.ops.aten.nonzero_numpy(inp)
32+
33+
assert len(res_out) == len(ref_out), "Number of output tensors should match"
34+
for res_t, ref_t in zip(res_out, ref_out):
35+
gems_assert_equal(res_t, ref_t)

0 commit comments

Comments
 (0)