Commit 9df5d8a7 authored by Ashwin Bharambe's avatar Ashwin Bharambe Committed by Facebook Github Bot

Allow for multi-threaded evaluation code

Summary:
Under certain settings, it is desirable to use multi-threaded
evaluation even though it is inefficient due to Python's GIL. This set of
changes allows for it.

Reviewed By: rbgirshick

Differential Revision: D6827125

fbshipit-source-id: 845647bd09198c35bb6d06c1a453a817d0dcf784
parent 19f56b90
...@@ -39,7 +39,6 @@ from caffe2.python import core ...@@ -39,7 +39,6 @@ from caffe2.python import core
from caffe2.python import workspace from caffe2.python import workspace
from core.config import cfg from core.config import cfg
from core.config import get_output_dir
from datasets import task_evaluation from datasets import task_evaluation
from datasets.json_dataset import JsonDataset from datasets.json_dataset import JsonDataset
from modeling import model_builder from modeling import model_builder
...@@ -54,9 +53,8 @@ import utils.subprocess as subprocess_utils ...@@ -54,9 +53,8 @@ import utils.subprocess as subprocess_utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def generate_rpn_on_dataset(multi_gpu=False): def generate_rpn_on_dataset(output_dir, multi_gpu=False, gpu_id=0):
"""Run inference on a dataset.""" """Run inference on a dataset."""
output_dir = get_output_dir(training=False)
dataset = JsonDataset(cfg.TEST.DATASET) dataset = JsonDataset(cfg.TEST.DATASET)
test_timer = Timer() test_timer = Timer()
test_timer.tic() test_timer.tic()
...@@ -67,7 +65,9 @@ def generate_rpn_on_dataset(multi_gpu=False): ...@@ -67,7 +65,9 @@ def generate_rpn_on_dataset(multi_gpu=False):
) )
else: else:
# Processes entire dataset range by default # Processes entire dataset range by default
_boxes, _scores, _ids, rpn_file = generate_rpn_on_range() _boxes, _scores, _ids, rpn_file = generate_rpn_on_range(
output_dir, gpu_id=gpu_id
)
test_timer.toc() test_timer.toc()
logger.info('Total inference time: {:.3f}s'.format(test_timer.average_time)) logger.info('Total inference time: {:.3f}s'.format(test_timer.average_time))
return evaluate_proposal_file(dataset, rpn_file, output_dir) return evaluate_proposal_file(dataset, rpn_file, output_dir)
...@@ -101,7 +101,7 @@ def multi_gpu_generate_rpn_on_dataset(num_images, output_dir): ...@@ -101,7 +101,7 @@ def multi_gpu_generate_rpn_on_dataset(num_images, output_dir):
return boxes, scores, ids, rpn_file return boxes, scores, ids, rpn_file
def generate_rpn_on_range(ind_range=None): def generate_rpn_on_range(output_dir, ind_range=None, gpu_id=0):
"""Run inference on all images in a dataset or over an index range of images """Run inference on all images in a dataset or over an index range of images
in a dataset using a single GPU. in a dataset using a single GPU.
""" """
...@@ -112,13 +112,14 @@ def generate_rpn_on_range(ind_range=None): ...@@ -112,13 +112,14 @@ def generate_rpn_on_range(ind_range=None):
assert cfg.MODEL.RPN_ONLY or cfg.MODEL.FASTER_RCNN assert cfg.MODEL.RPN_ONLY or cfg.MODEL.FASTER_RCNN
roidb, start_ind, end_ind, total_num_images = get_roidb(ind_range) roidb, start_ind, end_ind, total_num_images = get_roidb(ind_range)
output_dir = get_output_dir(training=False)
logger.info( logger.info(
'Output will be saved to: {:s}'.format(os.path.abspath(output_dir)) 'Output will be saved to: {:s}'.format(os.path.abspath(output_dir))
) )
model = model_builder.create(cfg.MODEL.TYPE, train=False) model = model_builder.create(cfg.MODEL.TYPE, train=False, gpu_id=gpu_id)
nu.initialize_from_weights_file(model, cfg.TEST.WEIGHTS) nu.initialize_gpu_from_weights_file(
model, cfg.TEST.WEIGHTS, gpu_id=gpu_id,
)
model_builder.add_inference_inputs(model) model_builder.add_inference_inputs(model)
workspace.CreateNet(model.net) workspace.CreateNet(model.net)
...@@ -127,7 +128,8 @@ def generate_rpn_on_range(ind_range=None): ...@@ -127,7 +128,8 @@ def generate_rpn_on_range(ind_range=None):
roidb, roidb,
start_ind=start_ind, start_ind=start_ind,
end_ind=end_ind, end_ind=end_ind,
total_num_images=total_num_images total_num_images=total_num_images,
gpu_id=gpu_id,
) )
cfg_yaml = yaml.dump(cfg) cfg_yaml = yaml.dump(cfg)
...@@ -144,7 +146,8 @@ def generate_rpn_on_range(ind_range=None): ...@@ -144,7 +146,8 @@ def generate_rpn_on_range(ind_range=None):
def generate_proposals_on_roidb( def generate_proposals_on_roidb(
model, roidb, start_ind=None, end_ind=None, total_num_images=None model, roidb, start_ind=None, end_ind=None, total_num_images=None,
gpu_id=0,
): ):
"""Generate RPN proposals on all images in an imdb.""" """Generate RPN proposals on all images in an imdb."""
_t = Timer() _t = Timer()
...@@ -159,7 +162,7 @@ def generate_proposals_on_roidb( ...@@ -159,7 +162,7 @@ def generate_proposals_on_roidb(
for i in range(num_images): for i in range(num_images):
roidb_ids[i] = roidb[i]['id'] roidb_ids[i] = roidb[i]['id']
im = cv2.imread(roidb[i]['image']) im = cv2.imread(roidb[i]['image'])
with c2_utils.NamedCudaScope(0): with c2_utils.NamedCudaScope(gpu_id):
_t.tic() _t.tic()
roidb_boxes[i], roidb_scores[i] = im_proposals(model, im) roidb_boxes[i], roidb_scores[i] = im_proposals(model, im)
_t.toc() _t.toc()
......
...@@ -31,7 +31,8 @@ import yaml ...@@ -31,7 +31,8 @@ import yaml
from caffe2.python import workspace from caffe2.python import workspace
from core.config import cfg from core.config import cfg
from core.config import get_output_dir from core.rpn_generator import generate_rpn_on_dataset
from core.rpn_generator import generate_rpn_on_range
from core.test import im_detect_all from core.test import im_detect_all
from datasets import task_evaluation from datasets import task_evaluation
from datasets.json_dataset import JsonDataset from datasets.json_dataset import JsonDataset
...@@ -47,9 +48,52 @@ import utils.vis as vis_utils ...@@ -47,9 +48,52 @@ import utils.vis as vis_utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def test_net_on_dataset(multi_gpu=False): def get_eval_functions():
# Determine which parent or child function should handle inference
if cfg.MODEL.RPN_ONLY:
child_func = generate_rpn_on_range
parent_func = generate_rpn_on_dataset
else:
# Generic case that handles all network types other than RPN-only nets
# and RetinaNet
child_func = test_net
parent_func = test_net_on_dataset
return parent_func, child_func
def run_inference(output_dir, ind_range=None, multi_gpu_testing=False, gpu_id=0):
parent_func, child_func = get_eval_functions()
is_parent = ind_range is None
if is_parent:
# Parent case:
# In this case we're either running inference on the entire dataset in a
# single process or (if multi_gpu_testing is True) using this process to
# launch subprocesses that each run inference on a range of the dataset
if len(cfg.TEST.DATASETS) == 0:
cfg.TEST.DATASETS = (cfg.TEST.DATASET, )
cfg.TEST.PROPOSAL_FILES = (cfg.TEST.PROPOSAL_FILE, )
all_results = {}
for i in range(len(cfg.TEST.DATASETS)):
cfg.TEST.DATASET = cfg.TEST.DATASETS[i]
if cfg.TEST.PRECOMPUTED_PROPOSALS:
cfg.TEST.PROPOSAL_FILE = cfg.TEST.PROPOSAL_FILES[i]
results = parent_func(output_dir, multi_gpu=multi_gpu_testing)
all_results.update(results)
return all_results
else:
# Subprocess child case:
# In this case test_net was called via subprocess.Popen to execute on a
# range of inputs on a single dataset (i.e., use cfg.TEST.DATASET and
# don't loop over cfg.TEST.DATASETS)
return child_func(output_dir, ind_range=ind_range, gpu_id=gpu_id)
def test_net_on_dataset(output_dir, multi_gpu=False, gpu_id=0):
"""Run inference on a dataset.""" """Run inference on a dataset."""
output_dir = get_output_dir(training=False)
dataset = JsonDataset(cfg.TEST.DATASET) dataset = JsonDataset(cfg.TEST.DATASET)
test_timer = Timer() test_timer = Timer()
test_timer.tic() test_timer.tic()
...@@ -59,7 +103,7 @@ def test_net_on_dataset(multi_gpu=False): ...@@ -59,7 +103,7 @@ def test_net_on_dataset(multi_gpu=False):
num_images, output_dir num_images, output_dir
) )
else: else:
all_boxes, all_segms, all_keyps = test_net() all_boxes, all_segms, all_keyps = test_net(output_dir, gpu_id=gpu_id)
test_timer.toc() test_timer.toc()
logger.info('Total inference time: {:.3f}s'.format(test_timer.average_time)) logger.info('Total inference time: {:.3f}s'.format(test_timer.average_time))
results = task_evaluation.evaluate_all( results = task_evaluation.evaluate_all(
...@@ -109,7 +153,7 @@ def multi_gpu_test_net_on_dataset(num_images, output_dir): ...@@ -109,7 +153,7 @@ def multi_gpu_test_net_on_dataset(num_images, output_dir):
return all_boxes, all_segms, all_keyps return all_boxes, all_segms, all_keyps
def test_net(ind_range=None): def test_net(output_dir, ind_range=None, gpu_id=0):
"""Run inference on all images in a dataset or over an index range of images """Run inference on all images in a dataset or over an index range of images
in a dataset using a single GPU. in a dataset using a single GPU.
""" """
...@@ -119,11 +163,11 @@ def test_net(ind_range=None): ...@@ -119,11 +163,11 @@ def test_net(ind_range=None):
'Use rpn_generate to generate proposals from RPN-only models' 'Use rpn_generate to generate proposals from RPN-only models'
assert cfg.TEST.DATASET != '', \ assert cfg.TEST.DATASET != '', \
'TEST.DATASET must be set to the dataset name to test' 'TEST.DATASET must be set to the dataset name to test'
output_dir = get_output_dir(training=False)
roidb, dataset, start_ind, end_ind, total_num_images = get_roidb_and_dataset( roidb, dataset, start_ind, end_ind, total_num_images = get_roidb_and_dataset(
ind_range ind_range
) )
model = initialize_model_from_cfg() model = initialize_model_from_cfg(gpu_id=gpu_id)
num_images = len(roidb) num_images = len(roidb)
num_classes = cfg.MODEL.NUM_CLASSES num_classes = cfg.MODEL.NUM_CLASSES
all_boxes, all_segms, all_keyps = empty_results(num_classes, num_images) all_boxes, all_segms, all_keyps = empty_results(num_classes, num_images)
...@@ -144,7 +188,7 @@ def test_net(ind_range=None): ...@@ -144,7 +188,7 @@ def test_net(ind_range=None):
box_proposals = None box_proposals = None
im = cv2.imread(entry['image']) im = cv2.imread(entry['image'])
with c2_utils.NamedCudaScope(0): with c2_utils.NamedCudaScope(gpu_id):
cls_boxes_i, cls_segms_i, cls_keyps_i = im_detect_all( cls_boxes_i, cls_segms_i, cls_keyps_i = im_detect_all(
model, im, box_proposals, timers model, im, box_proposals, timers
) )
...@@ -212,13 +256,13 @@ def test_net(ind_range=None): ...@@ -212,13 +256,13 @@ def test_net(ind_range=None):
return all_boxes, all_segms, all_keyps return all_boxes, all_segms, all_keyps
def initialize_model_from_cfg(): def initialize_model_from_cfg(gpu_id=0):
"""Initialize a model from the global cfg. Loads test-time weights and """Initialize a model from the global cfg. Loads test-time weights and
creates the networks in the Caffe2 workspace. creates the networks in the Caffe2 workspace.
""" """
model = model_builder.create(cfg.MODEL.TYPE, train=False) model = model_builder.create(cfg.MODEL.TYPE, train=False, gpu_id=gpu_id)
net_utils.initialize_from_weights_file( net_utils.initialize_gpu_from_weights_file(
model, cfg.TEST.WEIGHTS, broadcast=False model, cfg.TEST.WEIGHTS, gpu_id=gpu_id,
) )
model_builder.add_inference_inputs(model) model_builder.add_inference_inputs(model)
workspace.CreateNet(model.net) workspace.CreateNet(model.net)
......
...@@ -33,11 +33,10 @@ from caffe2.python import workspace ...@@ -33,11 +33,10 @@ from caffe2.python import workspace
from core.config import assert_and_infer_cfg from core.config import assert_and_infer_cfg
from core.config import cfg from core.config import cfg
from core.config import get_output_dir
from core.config import merge_cfg_from_file from core.config import merge_cfg_from_file
from core.config import merge_cfg_from_list from core.config import merge_cfg_from_list
from core.rpn_generator import generate_rpn_on_dataset from core.test_engine import run_inference
from core.rpn_generator import generate_rpn_on_range
from core.test_engine import test_net, test_net_on_dataset
from datasets import task_evaluation from datasets import task_evaluation
import utils.c2 import utils.c2
import utils.logging import utils.logging
...@@ -94,46 +93,17 @@ def parse_args(): ...@@ -94,46 +93,17 @@ def parse_args():
def main(ind_range=None, multi_gpu_testing=False): def main(ind_range=None, multi_gpu_testing=False):
# Determine which parent or child function should handle inference output_dir = get_output_dir(training=False)
if cfg.MODEL.RPN_ONLY: all_results = run_inference(
child_func = generate_rpn_on_range output_dir, ind_range=ind_range, multi_gpu_testing=multi_gpu_testing
parent_func = generate_rpn_on_dataset )
else: if not ind_range:
# Generic case that handles all network types other than RPN-only nets
child_func = test_net
parent_func = test_net_on_dataset
is_parent = ind_range is None
if is_parent:
# Parent case:
# In this case we're either running inference on the entire dataset in a
# single process or (if multi_gpu_testing is True) using this process to
# launch subprocesses that each run inference on a range of the dataset
if len(cfg.TEST.DATASETS) == 0:
cfg.TEST.DATASETS = (cfg.TEST.DATASET, )
cfg.TEST.PROPOSAL_FILES = (cfg.TEST.PROPOSAL_FILE, )
all_results = {}
for i in range(len(cfg.TEST.DATASETS)):
cfg.TEST.DATASET = cfg.TEST.DATASETS[i]
if cfg.TEST.PRECOMPUTED_PROPOSALS:
cfg.TEST.PROPOSAL_FILE = cfg.TEST.PROPOSAL_FILES[i]
results = parent_func(multi_gpu=multi_gpu_testing)
all_results.update(results)
task_evaluation.check_expected_results( task_evaluation.check_expected_results(
all_results, all_results,
atol=cfg.EXPECTED_RESULTS_ATOL, atol=cfg.EXPECTED_RESULTS_ATOL,
rtol=cfg.EXPECTED_RESULTS_RTOL rtol=cfg.EXPECTED_RESULTS_RTOL
) )
task_evaluation.log_copy_paste_friendly_results(all_results) task_evaluation.log_copy_paste_friendly_results(all_results)
else:
# Subprocess child case:
# In this case test_net was called via subprocess.Popen to execute on a
# range of inputs on a single dataset (i.e., use cfg.TEST.DATASET and
# don't loop over cfg.TEST.DATASETS)
child_func(ind_range=ind_range)
if __name__ == '__main__': if __name__ == '__main__':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment