Skip to content

Commit 5b2ccd9

Browse files
factnnclaude
andcommitted
feat: add nonzero_numpy operator with tests and benchmark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 09a2ad4 commit 5b2ccd9

5 files changed

Lines changed: 78 additions & 0 deletions

File tree

benchmark/test_nonzero_numpy.py

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

src/flag_gems/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ def torch_ge(v):
352352
("nll_loss2d_backward", nll_loss2d_backward),
353353
("nll_loss2d_forward", nll_loss2d_forward),
354354
("nonzero", nonzero),
355+
("nonzero_numpy", nonzero_numpy),
355356
("normal.Tensor_float", normal_tensor_float),
356357
("normal.Tensor_Tensor", normal_tensor_tensor),
357358
("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
@@ -225,6 +225,7 @@
225225
nll_loss_forward,
226226
)
227227
from flag_gems.ops.nonzero import nonzero
228+
from flag_gems.ops.nonzero_numpy import nonzero_numpy
228229
from flag_gems.ops.normal import (
229230
normal_,
230231
normal_float_tensor,
@@ -638,6 +639,7 @@
638639
"nll_loss_nd_forward",
639640
"nll_loss_nd_backward",
640641
"nonzero",
642+
"nonzero_numpy",
641643
"normal_float_tensor",
642644
"normal_tensor_float",
643645
"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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
import torch
3+
4+
import flag_gems
5+
6+
from .accuracy_utils import (
7+
BOOL_TYPES,
8+
FLOAT_DTYPES,
9+
INT_DTYPES,
10+
REDUCTION_SHAPES,
11+
gems_assert_equal,
12+
to_reference,
13+
)
14+
15+
NONZERO_SHAPES = REDUCTION_SHAPES
16+
17+
18+
@pytest.mark.nonzero_numpy
19+
@pytest.mark.parametrize("shape", NONZERO_SHAPES)
20+
@pytest.mark.parametrize("dtype", FLOAT_DTYPES + INT_DTYPES + BOOL_TYPES)
21+
def test_nonzero_numpy(shape, dtype):
22+
if dtype == torch.bool:
23+
inp = torch.randint(0, 2, shape, dtype=torch.int, device=flag_gems.device).to(
24+
dtype
25+
)
26+
elif dtype in INT_DTYPES:
27+
inp = torch.randint(-3, 3, shape, device=flag_gems.device).to(dtype)
28+
else:
29+
inp = torch.randn(shape, dtype=dtype, device=flag_gems.device)
30+
ref_inp = to_reference(inp, False)
31+
32+
ref_out = torch.ops.aten.nonzero_numpy(ref_inp)
33+
with flag_gems.use_gems():
34+
res_out = torch.ops.aten.nonzero_numpy(inp)
35+
36+
assert len(res_out) == len(ref_out), "Number of output tensors should match"
37+
for res_t, ref_t in zip(res_out, ref_out):
38+
gems_assert_equal(res_t, ref_t)

0 commit comments

Comments
 (0)