1
- from common_utils import set_rng_seed
1
+ from common_utils import needs_cuda , cpu_only
2
2
import math
3
3
import unittest
4
+ import pytest
4
5
5
6
import numpy as np
6
7
@@ -437,8 +438,8 @@ def test_msroialign_repr(self):
437
438
self .assertEqual (t .__repr__ (), expected_string )
438
439
439
440
440
- class NMSTester ( unittest . TestCase ) :
441
- def reference_nms (self , boxes , scores , iou_threshold ):
441
+ class TestNMS :
442
+ def _reference_nms (self , boxes , scores , iou_threshold ):
442
443
"""
443
444
Args:
444
445
box_scores (N, 5): boxes in corner-form and probabilities.
@@ -478,65 +479,73 @@ def _create_tensors_with_iou(self, N, iou_thresh):
478
479
scores = torch .rand (N )
479
480
return boxes , scores
480
481
481
- def test_nms (self ):
482
+ @cpu_only
483
+ @pytest .mark .parametrize ("iou" , (.2 , .5 , .8 ))
484
+ def test_nms_ref (self , iou ):
482
485
err_msg = 'NMS incompatible between CPU and reference implementation for IoU={}'
483
- for iou in [0.2 , 0.5 , 0.8 ]:
484
- boxes , scores = self ._create_tensors_with_iou (1000 , iou )
485
- keep_ref = self .reference_nms (boxes , scores , iou )
486
- keep = ops .nms (boxes , scores , iou )
487
- self .assertTrue (torch .allclose (keep , keep_ref ), err_msg .format (iou ))
488
- self .assertRaises (RuntimeError , ops .nms , torch .rand (4 ), torch .rand (3 ), 0.5 )
489
- self .assertRaises (RuntimeError , ops .nms , torch .rand (3 , 5 ), torch .rand (3 ), 0.5 )
490
- self .assertRaises (RuntimeError , ops .nms , torch .rand (3 , 4 ), torch .rand (3 , 2 ), 0.5 )
491
- self .assertRaises (RuntimeError , ops .nms , torch .rand (3 , 4 ), torch .rand (4 ), 0.5 )
492
-
493
- def test_qnms (self ):
486
+ boxes , scores = self ._create_tensors_with_iou (1000 , iou )
487
+ keep_ref = self ._reference_nms (boxes , scores , iou )
488
+ keep = ops .nms (boxes , scores , iou )
489
+ assert torch .allclose (keep , keep_ref ), err_msg .format (iou )
490
+
491
+ @cpu_only
492
+ def test_nms_input_errors (self ):
493
+ with pytest .raises (RuntimeError ):
494
+ ops .nms (torch .rand (4 ), torch .rand (3 ), 0.5 )
495
+ with pytest .raises (RuntimeError ):
496
+ ops .nms (torch .rand (3 , 5 ), torch .rand (3 ), 0.5 )
497
+ with pytest .raises (RuntimeError ):
498
+ ops .nms (torch .rand (3 , 4 ), torch .rand (3 , 2 ), 0.5 )
499
+ with pytest .raises (RuntimeError ):
500
+ ops .nms (torch .rand (3 , 4 ), torch .rand (4 ), 0.5 )
501
+
502
+ @cpu_only
503
+ @pytest .mark .parametrize ("iou" , (.2 , .5 , .8 ))
504
+ @pytest .mark .parametrize ("scale, zero_point" , ((1 , 0 ), (2 , 50 ), (3 , 10 )))
505
+ def test_qnms (self , iou , scale , zero_point ):
494
506
# Note: we compare qnms vs nms instead of qnms vs reference implementation.
495
507
# This is because with the int convertion, the trick used in _create_tensors_with_iou
496
508
# doesn't really work (in fact, nms vs reference implem will also fail with ints)
497
509
err_msg = 'NMS and QNMS give different results for IoU={}'
498
- for iou in [0.2 , 0.5 , 0.8 ]:
499
- for scale , zero_point in ((1 , 0 ), (2 , 50 ), (3 , 10 )):
500
- boxes , scores = self ._create_tensors_with_iou (1000 , iou )
501
- scores *= 100 # otherwise most scores would be 0 or 1 after int convertion
510
+ boxes , scores = self ._create_tensors_with_iou (1000 , iou )
511
+ scores *= 100 # otherwise most scores would be 0 or 1 after int convertion
502
512
503
- qboxes = torch .quantize_per_tensor (boxes , scale = scale , zero_point = zero_point ,
504
- dtype = torch .quint8 )
505
- qscores = torch .quantize_per_tensor (scores , scale = scale , zero_point = zero_point ,
506
- dtype = torch .quint8 )
513
+ qboxes = torch .quantize_per_tensor (boxes , scale = scale , zero_point = zero_point , dtype = torch .quint8 )
514
+ qscores = torch .quantize_per_tensor (scores , scale = scale , zero_point = zero_point , dtype = torch .quint8 )
507
515
508
- boxes = qboxes .dequantize ()
509
- scores = qscores .dequantize ()
516
+ boxes = qboxes .dequantize ()
517
+ scores = qscores .dequantize ()
510
518
511
- keep = ops .nms (boxes , scores , iou )
512
- qkeep = ops .nms (qboxes , qscores , iou )
519
+ keep = ops .nms (boxes , scores , iou )
520
+ qkeep = ops .nms (qboxes , qscores , iou )
513
521
514
- self . assertTrue ( torch .allclose (qkeep , keep ), err_msg .format (iou ) )
522
+ assert torch .allclose (qkeep , keep ), err_msg .format (iou )
515
523
516
- @unittest .skipIf (not torch .cuda .is_available (), "CUDA unavailable" )
517
- def test_nms_cuda (self , dtype = torch .float64 ):
524
+ @needs_cuda
525
+ @pytest .mark .parametrize ("iou" , (.2 , .5 , .8 ))
526
+ def test_nms_cuda (self , iou , dtype = torch .float64 ):
518
527
tol = 1e-3 if dtype is torch .half else 1e-5
519
528
err_msg = 'NMS incompatible between CPU and CUDA for IoU={}'
520
529
521
- for iou in [ 0.2 , 0.5 , 0.8 ]:
522
- boxes , scores = self . _create_tensors_with_iou ( 1000 , iou )
523
- r_cpu = ops .nms (boxes , scores , iou )
524
- r_cuda = ops . nms ( boxes . cuda (), scores . cuda (), iou )
525
-
526
- is_eq = torch . allclose ( r_cpu , r_cuda . cpu ())
527
- if not is_eq :
528
- # if the indices are not the same, ensure that it's because the scores
529
- # are duplicate
530
- is_eq = torch . allclose ( scores [ r_cpu ], scores [ r_cuda . cpu ()], rtol = tol , atol = tol )
531
- self . assertTrue ( is_eq , err_msg . format ( iou ))
532
-
533
- @unittest . skipIf ( not torch . cuda . is_available (), "CUDA unavailable" )
534
- def test_autocast ( self ):
535
- for dtype in ( torch . float , torch . half ):
536
- with torch .cuda .amp .autocast ():
537
- self .test_nms_cuda (dtype = dtype )
538
-
539
- @unittest . skipIf ( not torch . cuda . is_available (), "CUDA unavailable" )
530
+ boxes , scores = self . _create_tensors_with_iou ( 1000 , iou )
531
+ r_cpu = ops . nms ( boxes , scores , iou )
532
+ r_cuda = ops .nms (boxes . cuda () , scores . cuda () , iou )
533
+
534
+ is_eq = torch . allclose ( r_cpu , r_cuda . cpu ())
535
+ if not is_eq :
536
+ # if the indices are not the same, ensure that it's because the scores
537
+ # are duplicate
538
+ is_eq = torch . allclose ( scores [ r_cpu ], scores [ r_cuda . cpu ()], rtol = tol , atol = tol )
539
+ assert is_eq , err_msg . format ( iou )
540
+
541
+ @ needs_cuda
542
+ @pytest . mark . parametrize ( "iou" , ( .2 , .5 , .8 ) )
543
+ @ pytest . mark . parametrize ( "dtype" , ( torch . float , torch . half ))
544
+ def test_autocast ( self , iou , dtype ):
545
+ with torch .cuda .amp .autocast ():
546
+ self .test_nms_cuda (iou = iou , dtype = dtype )
547
+
548
+ @needs_cuda
540
549
def test_nms_cuda_float16 (self ):
541
550
boxes = torch .tensor ([[285.3538 , 185.5758 , 1193.5110 , 851.4551 ],
542
551
[285.1472 , 188.7374 , 1192.4984 , 851.0669 ],
@@ -546,8 +555,9 @@ def test_nms_cuda_float16(self):
546
555
iou_thres = 0.2
547
556
keep32 = ops .nms (boxes , scores , iou_thres )
548
557
keep16 = ops .nms (boxes .to (torch .float16 ), scores .to (torch .float16 ), iou_thres )
549
- self . assertTrue ( torch .all (torch .eq (keep32 , keep16 ) ))
558
+ assert torch .all (torch .eq (keep32 , keep16 ))
550
559
560
+ @cpu_only
551
561
def test_batched_nms_implementations (self ):
552
562
"""Make sure that both implementations of batched_nms yield identical results"""
553
563
@@ -564,11 +574,11 @@ def test_batched_nms_implementations(self):
564
574
keep_trick = ops .boxes ._batched_nms_coordinate_trick (boxes , scores , idxs , iou_threshold )
565
575
566
576
err_msg = "The vanilla and the trick implementation yield different nms outputs."
567
- self . assertTrue ( torch .allclose (keep_vanilla , keep_trick ), err_msg )
577
+ assert torch .allclose (keep_vanilla , keep_trick ), err_msg
568
578
569
579
# Also make sure an empty tensor is returned if boxes is empty
570
580
empty = torch .empty ((0 ,), dtype = torch .int64 )
571
- self . assertTrue ( torch .allclose (empty , ops .batched_nms (empty , None , None , None ) ))
581
+ assert torch .allclose (empty , ops .batched_nms (empty , None , None , None ))
572
582
573
583
574
584
class DeformConvTester (OpTester , unittest .TestCase ):
0 commit comments