11import argparse
2+ import os .path as osp
3+ import shutil
4+ import tempfile
25
3- import torch
46import mmcv
5- from mmcv .runner import load_checkpoint , parallel_test , obj_from_dict
6- from mmcv .parallel import scatter , collate , MMDataParallel
7+ import torch
8+ import torch .distributed as dist
9+ from mmcv .runner import load_checkpoint , get_dist_info
10+ from mmcv .parallel import MMDataParallel , MMDistributedDataParallel
711
8- from mmdet import datasets
12+ from mmdet . apis import init_dist
913from mmdet .core import results2json , coco_eval
10- from mmdet .datasets import build_dataloader
11- from mmdet .models import build_detector , detectors
14+ from mmdet .datasets import build_dataloader , get_dataset
15+ from mmdet .models import build_detector
1216
1317
14- def single_test (model , data_loader , show = False ):
18+ def single_gpu_test (model , data_loader , show = False ):
1519 model .eval ()
1620 results = []
1721 dataset = data_loader .dataset
@@ -22,7 +26,9 @@ def single_test(model, data_loader, show=False):
2226 results .append (result )
2327
2428 if show :
25- model .module .show_result (data , result , dataset .img_norm_cfg ,
29+ model .module .show_result (data ,
30+ result ,
31+ dataset .img_norm_cfg ,
2632 dataset = dataset .CLASSES )
2733
2834 batch_size = data ['img' ][0 ].size (0 )
@@ -31,22 +37,76 @@ def single_test(model, data_loader, show=False):
3137 return results
3238
3339
34- def _data_func (data , device_id ):
35- data = scatter (collate ([data ], samples_per_gpu = 1 ), [device_id ])[0 ]
36- return dict (return_loss = False , rescale = True , ** data )
40+ def multi_gpu_test (model , data_loader , tmpdir = None ):
41+ model .eval ()
42+ results = []
43+ dataset = data_loader .dataset
44+ rank , world_size = get_dist_info ()
45+ if rank == 0 :
46+ prog_bar = mmcv .ProgressBar (len (dataset ))
47+ for i , data in enumerate (data_loader ):
48+ with torch .no_grad ():
49+ result = model (return_loss = False , rescale = True , ** data )
50+ results .append (result )
51+
52+ if rank == 0 :
53+ batch_size = data ['img' ][0 ].size (0 )
54+ for _ in range (batch_size * world_size ):
55+ prog_bar .update ()
56+
57+ # collect results from all ranks
58+ results = collect_results (results , len (dataset ), tmpdir )
59+
60+ return results
61+
62+
63+ def collect_results (result_part , size , tmpdir = None ):
64+ rank , world_size = get_dist_info ()
65+ # create a tmp dir if it is not specified
66+ if tmpdir is None :
67+ MAX_LEN = 512
68+ # 32 is whitespace
69+ dir_tensor = torch .full ((MAX_LEN , ),
70+ 32 ,
71+ dtype = torch .uint8 ,
72+ device = 'cuda' )
73+ if rank == 0 :
74+ tmpdir = tempfile .mkdtemp ()
75+ tmpdir = torch .tensor (bytearray (tmpdir .encode ()),
76+ dtype = torch .uint8 ,
77+ device = 'cuda' )
78+ dir_tensor [:len (tmpdir )] = tmpdir
79+ dist .broadcast (dir_tensor , 0 )
80+ tmpdir = dir_tensor .cpu ().numpy ().tobytes ().decode ().rstrip ()
81+ else :
82+ mmcv .mkdir_or_exist (tmpdir )
83+ # dump the part result to the dir
84+ mmcv .dump (result_part , osp .join (tmpdir , 'part_{}.pkl' .format (rank )))
85+ dist .barrier ()
86+ # collect all parts
87+ if rank != 0 :
88+ return None
89+ else :
90+ # load results of all parts from tmp dir
91+ part_list = []
92+ for i in range (world_size ):
93+ part_file = osp .join (tmpdir , 'part_{}.pkl' .format (i ))
94+ part_list .append (mmcv .load (part_file ))
95+ # sort the results
96+ ordered_results = []
97+ for res in zip (* part_list ):
98+ ordered_results .extend (list (res ))
99+ # the dataloader may pad some samples
100+ ordered_results = ordered_results [:size ]
101+ # remove tmp dir
102+ shutil .rmtree (tmpdir )
103+ return ordered_results
37104
38105
39106def parse_args ():
40107 parser = argparse .ArgumentParser (description = 'MMDet test detector' )
41108 parser .add_argument ('config' , help = 'test config file path' )
42109 parser .add_argument ('checkpoint' , help = 'checkpoint file' )
43- parser .add_argument (
44- '--gpus' , default = 1 , type = int , help = 'GPU number used for testing' )
45- parser .add_argument (
46- '--proc_per_gpu' ,
47- default = 1 ,
48- type = int ,
49- help = 'Number of processes per GPU' )
50110 parser .add_argument ('--out' , help = 'output result file' )
51111 parser .add_argument (
52112 '--eval' ,
@@ -55,6 +115,12 @@ def parse_args():
55115 choices = ['proposal' , 'proposal_fast' , 'bbox' , 'segm' , 'keypoints' ],
56116 help = 'eval types' )
57117 parser .add_argument ('--show' , action = 'store_true' , help = 'show results' )
118+ parser .add_argument ('--tmpdir' , help = 'tmp dir for writing some results' )
119+ parser .add_argument ('--launcher' ,
120+ choices = ['none' , 'pytorch' , 'slurm' , 'mpi' ],
121+ default = 'none' ,
122+ help = 'job launcher' )
123+ parser .add_argument ('--local_rank' , type = int , default = 0 )
58124 args = parser .parse_args ()
59125 return args
60126
@@ -72,36 +138,36 @@ def main():
72138 cfg .model .pretrained = None
73139 cfg .data .test .test_mode = True
74140
75- dataset = obj_from_dict (cfg .data .test , datasets , dict (test_mode = True ))
76- if args .gpus == 1 :
77- model = build_detector (
78- cfg .model , train_cfg = None , test_cfg = cfg .test_cfg )
79- load_checkpoint (model , args .checkpoint )
141+ # init distributed env first, since logger depends on the dist info.
142+ if args .launcher == 'none' :
143+ distributed = False
144+ else :
145+ distributed = True
146+ init_dist (args .launcher , ** cfg .dist_params )
147+
148+ # build the dataloader
149+ # TODO: support multiple images per gpu (only minor changes are needed)
150+ dataset = get_dataset (cfg .data .test )
151+ data_loader = build_dataloader (dataset ,
152+ imgs_per_gpu = 1 ,
153+ workers_per_gpu = cfg .data .workers_per_gpu ,
154+ dist = distributed ,
155+ shuffle = False )
156+
157+ # build the model and load checkpoint
158+ model = build_detector (cfg .model , train_cfg = None , test_cfg = cfg .test_cfg )
159+ load_checkpoint (model , args .checkpoint , map_location = 'cpu' )
160+
161+ if not distributed :
80162 model = MMDataParallel (model , device_ids = [0 ])
81-
82- data_loader = build_dataloader (
83- dataset ,
84- imgs_per_gpu = 1 ,
85- workers_per_gpu = cfg .data .workers_per_gpu ,
86- num_gpus = 1 ,
87- dist = False ,
88- shuffle = False )
89- outputs = single_test (model , data_loader , args .show )
163+ outputs = single_gpu_test (model , data_loader , args .show )
90164 else :
91- model_args = cfg .model .copy ()
92- model_args .update (train_cfg = None , test_cfg = cfg .test_cfg )
93- model_type = getattr (detectors , model_args .pop ('type' ))
94- outputs = parallel_test (
95- model_type ,
96- model_args ,
97- args .checkpoint ,
98- dataset ,
99- _data_func ,
100- range (args .gpus ),
101- workers_per_gpu = args .proc_per_gpu )
102-
103- if args .out :
104- print ('writing results to {}' .format (args .out ))
165+ model = MMDistributedDataParallel (model .cuda ())
166+ outputs = multi_gpu_test (model , data_loader , args .tmpdir )
167+
168+ rank , _ = get_dist_info ()
169+ if args .out and rank == 0 :
170+ print ('\n writing results to {}' .format (args .out ))
105171 mmcv .dump (outputs , args .out )
106172 eval_types = args .eval
107173 if eval_types :
0 commit comments