Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
vision
Commits
8fb76e8f
Unverified
Commit
8fb76e8f
authored
May 17, 2021
by
Nicolas Hug
Committed by
GitHub
May 17, 2021
Browse files
Port NMS tests to use pytest and introduce the cpu_only decorator (#3852)
parent
e68d6b03
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
75 additions
and
52 deletions
+75
-52
test/common_utils.py
test/common_utils.py
+13
-0
test/test_ops.py
test/test_ops.py
+62
-52
No files found.
test/common_utils.py
View file @
8fb76e8f
...
@@ -409,6 +409,7 @@ def call_args_to_kwargs_only(call_args, *callable_or_arg_names):
...
@@ -409,6 +409,7 @@ def call_args_to_kwargs_only(call_args, *callable_or_arg_names):
def
cpu_and_gpu
():
def
cpu_and_gpu
():
# TODO: make this properly handle CircleCI
import
pytest
# noqa
import
pytest
# noqa
# ignore CPU tests in RE as they're already covered by another contbuild
# ignore CPU tests in RE as they're already covered by another contbuild
...
@@ -430,6 +431,7 @@ def cpu_and_gpu():
...
@@ -430,6 +431,7 @@ def cpu_and_gpu():
def
needs_cuda
(
test_func
):
def
needs_cuda
(
test_func
):
# TODO: make this properly handle CircleCI
import
pytest
# noqa
import
pytest
# noqa
if
IN_FBCODE
and
not
IN_RE_WORKER
:
if
IN_FBCODE
and
not
IN_RE_WORKER
:
...
@@ -441,3 +443,14 @@ def needs_cuda(test_func):
...
@@ -441,3 +443,14 @@ def needs_cuda(test_func):
return
test_func
return
test_func
else
:
else
:
return
pytest
.
mark
.
skip
(
reason
=
CUDA_NOT_AVAILABLE_MSG
)(
test_func
)
return
pytest
.
mark
.
skip
(
reason
=
CUDA_NOT_AVAILABLE_MSG
)(
test_func
)
def
cpu_only
(
test_func
):
# TODO: make this properly handle CircleCI
import
pytest
# noqa
if
IN_RE_WORKER
:
# The assumption is that all RE workers have GPUs.
return
pytest
.
mark
.
dont_collect
(
test_func
)
else
:
return
test_func
test/test_ops.py
View file @
8fb76e8f
from
common_utils
import
set_rng_seed
from
common_utils
import
needs_cuda
,
cpu_only
import
math
import
math
import
unittest
import
unittest
import
pytest
import
numpy
as
np
import
numpy
as
np
...
@@ -437,8 +438,8 @@ class MultiScaleRoIAlignTester(unittest.TestCase):
...
@@ -437,8 +438,8 @@ class MultiScaleRoIAlignTester(unittest.TestCase):
self
.
assertEqual
(
t
.
__repr__
(),
expected_string
)
self
.
assertEqual
(
t
.
__repr__
(),
expected_string
)
class
NMS
Test
er
(
unittest
.
TestCase
)
:
class
Test
NMS
:
def
reference_nms
(
self
,
boxes
,
scores
,
iou_threshold
):
def
_
reference_nms
(
self
,
boxes
,
scores
,
iou_threshold
):
"""
"""
Args:
Args:
box_scores (N, 5): boxes in corner-form and probabilities.
box_scores (N, 5): boxes in corner-form and probabilities.
...
@@ -478,32 +479,39 @@ class NMSTester(unittest.TestCase):
...
@@ -478,32 +479,39 @@ class NMSTester(unittest.TestCase):
scores
=
torch
.
rand
(
N
)
scores
=
torch
.
rand
(
N
)
return
boxes
,
scores
return
boxes
,
scores
def
test_nms
(
self
):
@
cpu_only
@
pytest
.
mark
.
parametrize
(
"iou"
,
(.
2
,
.
5
,
.
8
))
def
test_nms_ref
(
self
,
iou
):
err_msg
=
'NMS incompatible between CPU and reference implementation for IoU={}'
err_msg
=
'NMS incompatible between CPU and reference implementation for IoU={}'
for
iou
in
[
0.2
,
0.5
,
0.8
]:
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
keep_ref
=
self
.
reference_nms
(
boxes
,
scores
,
iou
)
keep_ref
=
self
.
_
reference_nms
(
boxes
,
scores
,
iou
)
keep
=
ops
.
nms
(
boxes
,
scores
,
iou
)
keep
=
ops
.
nms
(
boxes
,
scores
,
iou
)
self
.
assertTrue
(
torch
.
allclose
(
keep
,
keep_ref
),
err_msg
.
format
(
iou
))
assert
torch
.
allclose
(
keep
,
keep_ref
),
err_msg
.
format
(
iou
)
self
.
assertRaises
(
RuntimeError
,
ops
.
nms
,
torch
.
rand
(
4
),
torch
.
rand
(
3
),
0.5
)
self
.
assertRaises
(
RuntimeError
,
ops
.
nms
,
torch
.
rand
(
3
,
5
),
torch
.
rand
(
3
),
0.5
)
@
cpu_only
self
.
assertRaises
(
RuntimeError
,
ops
.
nms
,
torch
.
rand
(
3
,
4
),
torch
.
rand
(
3
,
2
),
0.5
)
def
test_nms_input_errors
(
self
):
self
.
assertRaises
(
RuntimeError
,
ops
.
nms
,
torch
.
rand
(
3
,
4
),
torch
.
rand
(
4
),
0.5
)
with
pytest
.
raises
(
RuntimeError
):
ops
.
nms
(
torch
.
rand
(
4
),
torch
.
rand
(
3
),
0.5
)
def
test_qnms
(
self
):
with
pytest
.
raises
(
RuntimeError
):
ops
.
nms
(
torch
.
rand
(
3
,
5
),
torch
.
rand
(
3
),
0.5
)
with
pytest
.
raises
(
RuntimeError
):
ops
.
nms
(
torch
.
rand
(
3
,
4
),
torch
.
rand
(
3
,
2
),
0.5
)
with
pytest
.
raises
(
RuntimeError
):
ops
.
nms
(
torch
.
rand
(
3
,
4
),
torch
.
rand
(
4
),
0.5
)
@
cpu_only
@
pytest
.
mark
.
parametrize
(
"iou"
,
(.
2
,
.
5
,
.
8
))
@
pytest
.
mark
.
parametrize
(
"scale, zero_point"
,
((
1
,
0
),
(
2
,
50
),
(
3
,
10
)))
def
test_qnms
(
self
,
iou
,
scale
,
zero_point
):
# Note: we compare qnms vs nms instead of qnms vs reference implementation.
# Note: we compare qnms vs nms instead of qnms vs reference implementation.
# This is because with the int convertion, the trick used in _create_tensors_with_iou
# This is because with the int convertion, the trick used in _create_tensors_with_iou
# doesn't really work (in fact, nms vs reference implem will also fail with ints)
# doesn't really work (in fact, nms vs reference implem will also fail with ints)
err_msg
=
'NMS and QNMS give different results for IoU={}'
err_msg
=
'NMS and QNMS give different results for IoU={}'
for
iou
in
[
0.2
,
0.5
,
0.8
]:
for
scale
,
zero_point
in
((
1
,
0
),
(
2
,
50
),
(
3
,
10
)):
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
scores
*=
100
# otherwise most scores would be 0 or 1 after int convertion
scores
*=
100
# otherwise most scores would be 0 or 1 after int convertion
qboxes
=
torch
.
quantize_per_tensor
(
boxes
,
scale
=
scale
,
zero_point
=
zero_point
,
qboxes
=
torch
.
quantize_per_tensor
(
boxes
,
scale
=
scale
,
zero_point
=
zero_point
,
dtype
=
torch
.
quint8
)
dtype
=
torch
.
quint8
)
qscores
=
torch
.
quantize_per_tensor
(
scores
,
scale
=
scale
,
zero_point
=
zero_point
,
dtype
=
torch
.
quint8
)
qscores
=
torch
.
quantize_per_tensor
(
scores
,
scale
=
scale
,
zero_point
=
zero_point
,
dtype
=
torch
.
quint8
)
boxes
=
qboxes
.
dequantize
()
boxes
=
qboxes
.
dequantize
()
scores
=
qscores
.
dequantize
()
scores
=
qscores
.
dequantize
()
...
@@ -511,14 +519,14 @@ class NMSTester(unittest.TestCase):
...
@@ -511,14 +519,14 @@ class NMSTester(unittest.TestCase):
keep
=
ops
.
nms
(
boxes
,
scores
,
iou
)
keep
=
ops
.
nms
(
boxes
,
scores
,
iou
)
qkeep
=
ops
.
nms
(
qboxes
,
qscores
,
iou
)
qkeep
=
ops
.
nms
(
qboxes
,
qscores
,
iou
)
self
.
assert
True
(
torch
.
allclose
(
qkeep
,
keep
),
err_msg
.
format
(
iou
)
)
assert
torch
.
allclose
(
qkeep
,
keep
),
err_msg
.
format
(
iou
)
@
unittest
.
skipIf
(
not
torch
.
cuda
.
is_available
(),
"CUDA unavailable"
)
@
needs_cuda
def
test_nms_cuda
(
self
,
dtype
=
torch
.
float64
):
@
pytest
.
mark
.
parametrize
(
"iou"
,
(.
2
,
.
5
,
.
8
))
def
test_nms_cuda
(
self
,
iou
,
dtype
=
torch
.
float64
):
tol
=
1e-3
if
dtype
is
torch
.
half
else
1e-5
tol
=
1e-3
if
dtype
is
torch
.
half
else
1e-5
err_msg
=
'NMS incompatible between CPU and CUDA for IoU={}'
err_msg
=
'NMS incompatible between CPU and CUDA for IoU={}'
for
iou
in
[
0.2
,
0.5
,
0.8
]:
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
boxes
,
scores
=
self
.
_create_tensors_with_iou
(
1000
,
iou
)
r_cpu
=
ops
.
nms
(
boxes
,
scores
,
iou
)
r_cpu
=
ops
.
nms
(
boxes
,
scores
,
iou
)
r_cuda
=
ops
.
nms
(
boxes
.
cuda
(),
scores
.
cuda
(),
iou
)
r_cuda
=
ops
.
nms
(
boxes
.
cuda
(),
scores
.
cuda
(),
iou
)
...
@@ -528,15 +536,16 @@ class NMSTester(unittest.TestCase):
...
@@ -528,15 +536,16 @@ class NMSTester(unittest.TestCase):
# if the indices are not the same, ensure that it's because the scores
# if the indices are not the same, ensure that it's because the scores
# are duplicate
# are duplicate
is_eq
=
torch
.
allclose
(
scores
[
r_cpu
],
scores
[
r_cuda
.
cpu
()],
rtol
=
tol
,
atol
=
tol
)
is_eq
=
torch
.
allclose
(
scores
[
r_cpu
],
scores
[
r_cuda
.
cpu
()],
rtol
=
tol
,
atol
=
tol
)
self
.
assert
True
(
is_eq
,
err_msg
.
format
(
iou
)
)
assert
is_eq
,
err_msg
.
format
(
iou
)
@
unittest
.
skipIf
(
not
torch
.
cuda
.
is_available
(),
"CUDA unavailable"
)
@
needs_cuda
def
test_autocast
(
self
):
@
pytest
.
mark
.
parametrize
(
"iou"
,
(.
2
,
.
5
,
.
8
))
for
dtype
in
(
torch
.
float
,
torch
.
half
):
@
pytest
.
mark
.
parametrize
(
"dtype"
,
(
torch
.
float
,
torch
.
half
))
def
test_autocast
(
self
,
iou
,
dtype
):
with
torch
.
cuda
.
amp
.
autocast
():
with
torch
.
cuda
.
amp
.
autocast
():
self
.
test_nms_cuda
(
dtype
=
dtype
)
self
.
test_nms_cuda
(
iou
=
iou
,
dtype
=
dtype
)
@
unittest
.
skipIf
(
not
torch
.
cuda
.
is_available
(),
"CUDA unavailable"
)
@
needs_cuda
def
test_nms_cuda_float16
(
self
):
def
test_nms_cuda_float16
(
self
):
boxes
=
torch
.
tensor
([[
285.3538
,
185.5758
,
1193.5110
,
851.4551
],
boxes
=
torch
.
tensor
([[
285.3538
,
185.5758
,
1193.5110
,
851.4551
],
[
285.1472
,
188.7374
,
1192.4984
,
851.0669
],
[
285.1472
,
188.7374
,
1192.4984
,
851.0669
],
...
@@ -546,8 +555,9 @@ class NMSTester(unittest.TestCase):
...
@@ -546,8 +555,9 @@ class NMSTester(unittest.TestCase):
iou_thres
=
0.2
iou_thres
=
0.2
keep32
=
ops
.
nms
(
boxes
,
scores
,
iou_thres
)
keep32
=
ops
.
nms
(
boxes
,
scores
,
iou_thres
)
keep16
=
ops
.
nms
(
boxes
.
to
(
torch
.
float16
),
scores
.
to
(
torch
.
float16
),
iou_thres
)
keep16
=
ops
.
nms
(
boxes
.
to
(
torch
.
float16
),
scores
.
to
(
torch
.
float16
),
iou_thres
)
self
.
assert
True
(
torch
.
all
(
torch
.
eq
(
keep32
,
keep16
))
)
assert
torch
.
all
(
torch
.
eq
(
keep32
,
keep16
))
@
cpu_only
def
test_batched_nms_implementations
(
self
):
def
test_batched_nms_implementations
(
self
):
"""Make sure that both implementations of batched_nms yield identical results"""
"""Make sure that both implementations of batched_nms yield identical results"""
...
@@ -564,11 +574,11 @@ class NMSTester(unittest.TestCase):
...
@@ -564,11 +574,11 @@ class NMSTester(unittest.TestCase):
keep_trick
=
ops
.
boxes
.
_batched_nms_coordinate_trick
(
boxes
,
scores
,
idxs
,
iou_threshold
)
keep_trick
=
ops
.
boxes
.
_batched_nms_coordinate_trick
(
boxes
,
scores
,
idxs
,
iou_threshold
)
err_msg
=
"The vanilla and the trick implementation yield different nms outputs."
err_msg
=
"The vanilla and the trick implementation yield different nms outputs."
self
.
assert
True
(
torch
.
allclose
(
keep_vanilla
,
keep_trick
),
err_msg
)
assert
torch
.
allclose
(
keep_vanilla
,
keep_trick
),
err_msg
# Also make sure an empty tensor is returned if boxes is empty
# Also make sure an empty tensor is returned if boxes is empty
empty
=
torch
.
empty
((
0
,),
dtype
=
torch
.
int64
)
empty
=
torch
.
empty
((
0
,),
dtype
=
torch
.
int64
)
self
.
assert
True
(
torch
.
allclose
(
empty
,
ops
.
batched_nms
(
empty
,
None
,
None
,
None
))
)
assert
torch
.
allclose
(
empty
,
ops
.
batched_nms
(
empty
,
None
,
None
,
None
))
class
DeformConvTester
(
OpTester
,
unittest
.
TestCase
):
class
DeformConvTester
(
OpTester
,
unittest
.
TestCase
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment