Skip to content

Commit 0f498d1

Browse files
Add nonzero_numpy operator implementation, tests and benchmark
1 parent 015d315 commit 0f498d1

5 files changed

Lines changed: 55 additions & 0 deletions

File tree

benchmark/test_reduction_perf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ def mse_loss_input_fn(shape, cur_dtype, device):
159159
FLOAT_DTYPES + INT_DTYPES + BOOL_DTYPES,
160160
marks=pytest.mark.nonzero,
161161
),
162+
pytest.param(
163+
"nonzero_numpy",
164+
torch.ops.aten.nonzero_numpy,
165+
unary_input_fn,
166+
FLOAT_DTYPES + INT_DTYPES + BOOL_DTYPES,
167+
marks=pytest.mark.nonzero_numpy,
168+
),
162169
pytest.param(
163170
"cross_entropy_loss",
164171
torch.nn.functional.cross_entropy,

src/flag_gems/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ def torch_ge(v):
258258
("nll_loss2d_backward", nll_loss2d_backward),
259259
("nll_loss2d_forward", nll_loss2d_forward),
260260
("nonzero", nonzero),
261+
("nonzero_numpy", nonzero_numpy),
261262
("normal.float_Tensor", normal_float_tensor),
262263
("normal.Tensor_float", normal_tensor_float),
263264
("normal.Tensor_Tensor", normal_tensor_tensor),

src/flag_gems/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
nll_loss_forward,
153153
)
154154
from flag_gems.ops.nonzero import nonzero
155+
from flag_gems.ops.nonzero_numpy import nonzero_numpy
155156
from flag_gems.ops.normal import (
156157
normal_,
157158
normal_float_tensor,
@@ -435,6 +436,7 @@
435436
"nll_loss2d_backward",
436437
"nll_loss2d_forward",
437438
"nonzero",
439+
"nonzero_numpy",
438440
"normal_float_tensor",
439441
"normal_tensor_float",
440442
"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_reduction_ops.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,29 @@ def test_accuracy_nonzero(shape, dtype):
513513
gems_assert_equal(res_out, ref_out)
514514

515515

516+
@pytest.mark.nonzero_numpy
517+
@pytest.mark.parametrize("shape", NONZERO_SHAPES)
518+
@pytest.mark.parametrize("dtype", FLOAT_DTYPES + INT_DTYPES + [torch.bool])
519+
def test_accuracy_nonzero_numpy(shape, dtype):
520+
if dtype == torch.bool:
521+
inp = torch.randint(0, 2, shape, dtype=torch.int, device=flag_gems.device).to(
522+
dtype
523+
)
524+
elif dtype in INT_DTYPES:
525+
inp = torch.randint(-3, 3, shape, device=flag_gems.device).to(dtype)
526+
else:
527+
inp = torch.randn(shape, dtype=dtype, device=flag_gems.device)
528+
ref_inp = to_reference(inp, False)
529+
530+
ref_out = torch.ops.aten.nonzero_numpy(ref_inp)
531+
with flag_gems.use_gems():
532+
res_out = torch.ops.aten.nonzero_numpy(inp)
533+
534+
assert len(res_out) == len(ref_out), "Number of output tensors should match"
535+
for res_t, ref_t in zip(res_out, ref_out):
536+
gems_assert_equal(res_t, ref_t)
537+
538+
516539
@pytest.mark.count_nonzero
517540
@pytest.mark.parametrize("shape", REDUCTION_SHAPES)
518541
@pytest.mark.parametrize("dtype", FLOAT_DTYPES + INT_DTYPES + [torch.bool])

0 commit comments

Comments
 (0)