From 4b4172ae0f6e1c030232c4b7ced2bfe5366a1380 Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 25 Jan 2019 15:02:21 +0900 Subject: [PATCH 01/15] [Relay][Frontend] Add Caffe2 Support --- python/tvm/relay/frontend/caffe2.py | 566 ++++++++++++++++++ .../frontend/caffe2/model_zoo/__init__.py | 23 + .../frontend/caffe2/model_zoo/squeezenet.py | 132 ++++ tests/python/frontend/caffe2/test_forward.py | 88 +++ tests/python/frontend/caffe2/test_graph.py | 21 + tests/scripts/task_python_frontend.sh | 4 + 6 files changed, 834 insertions(+) create mode 100755 python/tvm/relay/frontend/caffe2.py create mode 100644 tests/python/frontend/caffe2/model_zoo/__init__.py create mode 100644 tests/python/frontend/caffe2/model_zoo/squeezenet.py create mode 100644 tests/python/frontend/caffe2/test_forward.py create mode 100755 tests/python/frontend/caffe2/test_graph.py diff --git a/python/tvm/relay/frontend/caffe2.py b/python/tvm/relay/frontend/caffe2.py new file mode 100755 index 000000000000..b0c68c9a4b05 --- /dev/null +++ b/python/tvm/relay/frontend/caffe2.py @@ -0,0 +1,566 @@ +# pylint: disable=import-self, invalid-name, line-too-long, unused-argument +"""Caffe2 frontend""" +from __future__ import absolute_import as _abs +import tvm +from .. import ir_pass +from .. import expr as _expr +from .. import op as _op +from ... import nd as _nd +from .common import AttrCvt, Renamer +from .common import get_relay_op, new_var, infer_channels + +__all__ = ['from_caffe2'] + +def dimension_picker(prefix, surfix=''): + def _impl(attr): + kernel = attr['kernel_shape'] + if len(kernel) == 2: + return prefix + '2d' + surfix + else: + raise NotImplementedError("Only 2d kernel supported.") + + return _impl + + +def revert_caffe2_pad(pads): + """Caffe2 requires two times the normal padding.""" + if len(pads) == 4: + pads = pads[:2] + elif len(pads) == 2: + pass + else: + raise ValueError("Invalid caffe2 type padding: {}".format(pads)) + return pads + + +def dimension_constraint(): + def _dim_check(args): + if len(args['kernel_shape']) == 2: + return True + return False + + return _dim_check, "Only 2d kernel supported." + + +def _clean_up_pool_args(args): + """ A helper function to clean up common arguments in conv and pooling ops. + """ + assert isinstance(args, dict) + + if 'stride_h' in args and 'stride_w' in args: + assert 'stride' not in args and 'strides' not in args + args['strides'] = [args['stride_h'], args['stride_w']] + args.pop('stride_h') + args.pop('stride_w') + elif 'stride' in args: + args['strides'] = [args['stride'], args['stride']] + args.pop('stride') + + # rename 'kernel', 'kernels', to 'kernel_shape' + if 'kernel_h' in args and 'kernel_w' in args: + assert 'kernel' not in args and 'kernels' not in args + args['kernel_shape'] = [args['kernel_h'], args['kernel_w']] + args.pop('kernel_h') + args.pop('kernel_w') + elif 'kernel' in args: + args['kernel_shape'] = [args['kernel'], args['kernel']] + args.pop('kernel') + elif 'kernels' in args: + args['kernel_shape'] = args['kernels'] + args.pop('kernels') + + if 'pad_t' in args and 'pad_l' in args and 'pad_b' in args and 'pad_r' in args: + assert 'pad' not in args and 'pads' not in args + args['pads'] = [ + args['pad_t'], args['pad_l'], args['pad_b'], args['pad_r'] + ] + for pad in ['pad_t', 'pad_l', 'pad_b', 'pad_r']: + args.pop(pad) + elif 'pad' in args: + args['pads'] = [args['pad'], args['pad']] + args.pop('pad') + + if 'dilation_h' in args and 'dilation_w' in args: + assert 'dilation' not in args and 'dilations' not in args + args['dilations'] = [args['dilation_h'], args['dilation_w']] + args.pop('dilation_h') + args.pop('dilation_w') + elif 'dilation' in args: + args['dilations'] = [args['dilation'], args['dilation']] + args.pop('dilation') + + return args + + +class Caffe2OpConverter(object): + """ A helper class for holding Caffe2 op converters. + """ + + @classmethod + def get_converter(cls): + """ Get converter. + + :return: converter, which should be `_impl`. + """ + + if hasattr(cls, '_impl'): + return getattr(cls, '_impl') + else: + raise NotImplementedError('{} not implemented'.format( + cls.__name__)) + + +_caffe2_internal_args = [ + # nnpack args + 'algo', + 'convolution_transform_strategy', + 'float16_compute', + 'shared_buffer', + + # training args + 'init_params', + 'cudnn_exhaustive_search', + 'exhaustive_search', + + # training args + 'adj', + 'hwgq', + + # args that we don't care + 'legacy_pad', +] + + +class Elemwise(Caffe2OpConverter): + """ A helper class for elemwise op converters. + """ + name = '' + @classmethod + def _math_name_picker(cls, suffix): + + def _impl(attr): + if attr.get('broadcast', 0): + return 'broadcast_' + suffix + return 'elemwise_' + suffix + + return _impl + + @classmethod + def _impl(cls, inputs, args, params): + assert len(inputs) == 2, "Math op take 2 inputs, {} given".format( + len(inputs)) + op_name = cls._math_name_picker(cls.name)(args) + axis = int(args.get('axis', 0)) + conv_ops = ["conv2d", "conv2d_transpose"] + if op_name == 'broadcast_add' and inputs[0].attr('op_name') in conv_ops: + # TODO(zhreshold): remove hard coded infershape + inputs[1] = _op.expand_dims(inputs[1], axis=axis, num_newaxis=2) + return get_relay_op(op_name)(*inputs) + + +class Add(Elemwise): + """ Operator converter for Add. + """ + name = 'add' + + +class Pool(Caffe2OpConverter): + """ A helper class for pool op converters. + """ + + name = '' + @classmethod + def _impl(cls, inputs, args, params): + _clean_up_pool_args(args) + if 'global_pooling' in args and args['global_pooling'] == 1: + op_name = dimension_picker('global_' + cls.name) + return get_relay_op(op_name(args))(*inputs) + + return AttrCvt( + op_name=dimension_picker(cls.name), + transforms={ + 'kernel_shape': 'pool_size', + 'pads': ('padding', (0, 0), revert_caffe2_pad), + 'strides': 'strides', + }, + ignores=['dilations', 'order', 'legacy_pad', 'global_pooling'], + extras={'ceil_mode': False}, + custom_check=dimension_constraint())(inputs, args, params) + + +class AveragePool(Pool): + name = 'avg_pool' + + +class MaxPool(Pool): + name = 'max_pool' + + +class Conv(Caffe2OpConverter): + """ Operator converter for Conv. + """ + + @classmethod + def _impl(cls, inputs, args, params): + # get number of channels + channels = infer_channels(inputs[1]) + args['channels'] = channels + _clean_up_pool_args(args) + out = AttrCvt( + op_name=dimension_picker('conv'), + transforms={ + 'group': ('groups', 1), + 'kernel_shape': 'kernel_size', + 'pads': ('padding', (0, 0), revert_caffe2_pad), + 'strides': 'strides', + 'dilations': ('dilation', (1, 1)), + 'order': ('data_layout', ("NCHW"), lambda x: x if isinstance(x, str) else x.decode('UTF-8')), + }, + excludes=[], + ignores=[], + custom_check=dimension_constraint())(inputs[:2], args, params) + use_bias = len(inputs) == 3 + if use_bias: + out = _op.nn.bias_add(out, inputs[2]) + return out + + +class Concat(Caffe2OpConverter): + """ Operator converter for Concat. + """ + + @classmethod + def _impl(cls, inputs, args, params): + def _get_axis_from_order_str(order): + order = order if isinstance(order, str) else order.decode('UTF-8') + if order == 'NCHW': + return 1 + elif order == 'NHWC': + return 3 + else: + raise RuntimeError( + "Unsupported storage order: {} in caffe2".format(order)) + + return AttrCvt( + op_name='concatenate', + transforms={ + 'order': ('axis', (1), _get_axis_from_order_str), + }, + excludes=['add_axis'])((inputs,), args, params) + + +class NormalizePlanarYUV(Caffe2OpConverter): + """ Operator converter for NormalizePlanarYUV. + caffe2 definition: https://github.com/pytorch/pytorch/blob/master/caffe2/operators/norm_planar_yuv_op.cc + """ + + @classmethod + def _impl(cls, inputs, args, params): + assert len(inputs) == 3 + mean = _op.expand_dims(inputs[1], axis=2, num_newaxis=2) + std = _op.expand_dims(inputs[2], axis=2, num_newaxis=2) + + return _op.broadcast_divide(_op.subtract(inputs[0], mean), std) + + +class ResizeNearest(Caffe2OpConverter): + """ Operator converter for Upsample (nearest mode). + """ + + @classmethod + def _impl(cls, inputs, args, params): + width_scale = args['width_scale'] if 'width_scale' in args else 1 + height_scale = args['height_scale'] if 'height_scale' in args else 1 + assert width_scale == height_scale + + return _op.nn.upsampling( + inputs[0], scale=int(width_scale), method="NEAREST_NEIGHBOR") + + +class Sum(Caffe2OpConverter): + """ Operator converter for Sum. + """ + + @classmethod + def _impl(cls, inputs, args, params): + # Sum Operator + for in_index in range(len(inputs) - 1): + inputs[in_index + 1] = _op.add(inputs[in_index], inputs[in_index + 1]) + + return inputs[len(inputs) - 1] + + +class Softmax(Caffe2OpConverter): + """ Operator converter for Softmax. + """ + + @classmethod + def _impl(cls, inputs, args, params): + # set default value when axis is not set in the model + if 'axis' not in args: + args['axis'] = 1 + return AttrCvt('softmax', transforms={'axis': ('axis', args['axis'])})(inputs, args, params) + + +class FC(Caffe2OpConverter): + """ Operator converter for FC. + """ + + @classmethod + def _impl(cls, inputs, args, params): + inputs[0] = _op.nn.batch_flatten(inputs[0]) + units = infer_channels(inputs[1]) + res = _op.nn.dense(inputs[0], inputs[1], units=units) + use_bias = len(inputs) == 3 + if use_bias: + res = _op.nn.bias_add(res, inputs[2]) + return res + + +class SpatialBN(Caffe2OpConverter): + """ Operator converter for SpatialBN. + """ + + @classmethod + def _impl(cls, inputs, args, params): + return AttrCvt( + op_name='batch_norm', + disables=['momentum'], + ignores=[ + 'order', 'spatial', 'is_test', 'consumed_inputs', 'num_batches' + ])(inputs, args, params) + + +# compatible operators that do NOT require any conversion. +_identity_list = [] + +# _convert_map defines maps of name to converter functor(callable) +# for 1 to 1 mapping, use Renamer if nothing but name is different +# use AttrCvt if attributes need to be converted +# for 1 to N mapping(composed), use custom callable functions +# for N to 1 mapping, currently not supported(?) + +# Minimal set of ops for squeezenet and resnet50 +def _get_convert_map(): + return { + # caffe2 common operators + 'Add': Add.get_converter(), + 'Sum': Sum.get_converter(), + 'Softmax': Softmax.get_converter(), + + # nn + 'AveragePool': AveragePool.get_converter(), + 'MaxPool': MaxPool.get_converter(), + 'Conv': Conv.get_converter(), + 'Concat': Concat.get_converter(), + 'FC': FC.get_converter(), + 'SpatialBN': SpatialBN.get_converter(), + 'ResizeNearest': ResizeNearest.get_converter(), + 'Relu': AttrCvt('relu', {}, ignores=['order']), + 'Sigmoid': Renamer('sigmoid'), + 'Dropout': AttrCvt('dropout', {'ratio': 'rate'}, ignores=['is_test']), + + # c2 image preprocessing ops + 'NormalizePlanarYUV': NormalizePlanarYUV.get_converter(), + } + + +class Caffe2NetDef(object): + """A helper class for handling Relay expression copying from pb2.GraphProto. + Definition: https://github.com/pytorch/pytorch/blob/master/caffe2/proto/caffe2.proto + """ + + def __init__(self, shape, dtype): + self._nodes = {} + self._params = {} + self._visited_nodes = set() + self._ops = {} + self._shape = shape + self._dtype = dtype + + def from_caffe2(self, init_net, predict_net): + """Construct Relay expression from caffe2 graph. + + Parameters + ---------- + init_net : protobuf object + predict_net : protobuf object + + Returns + ------- + func : tvm.relay.expr.Function + Compatible relay function + params : dict + A dict of name: tvm.nd.array pairs, used as pretrained weights + """ + from caffe2.python import workspace + workspace.RunNetOnce(init_net) + + # Input + input_name = predict_net.op[0].input[0] + + # Params + self._params = {} + used_blobs = set() + for c2_op in predict_net.op: + for i in c2_op.input: + used_blobs.add(i) + for blob in workspace.Blobs(): + if blob in used_blobs and blob != input_name: + self._params[blob] = _nd.array(workspace.FetchBlob(blob)) + + # Variables + self._nodes = {} + for blob in predict_net.external_input: + if blob in self._params: + self._nodes[blob] = new_var(blob, shape=self._params[blob].shape, dtype=self._params[blob].dtype) + else: + shape = self._shape[blob] if blob in self._shape else () + if isinstance(self._dtype, dict) and blob in self._dtype: + dtype = str(self._dtype[blob]) + elif isinstance(self._dtype, str): + dtype = self._dtype + else: + dtype = "float32" + self._nodes[blob] = new_var(blob, shape=shape, dtype=dtype) + + # Ops + for c2_op in predict_net.op: + for blob in c2_op.output: + self._ops[blob] = c2_op + + for c2_op in predict_net.op: + self._process_op(c2_op) + + # Outputs + out = [] + for blob in predict_net.external_output: + out.append(self._nodes[blob]) + + if len(out) > 1: + outputs = _expr.Tuple(out) + else: + outputs = out[0] + + func = _expr.Function(ir_pass.free_vars(outputs), outputs) + + return func, self._params + + def _get_node(self, blob): + """Get the Symbol of blob and detect cyclic dependency in the graph.""" + if blob in self._nodes: + return self._nodes[blob] + + assert blob not in self._visited_nodes, 'Cyclic dependency in the graph (in {})'.format( + blob) + self._visited_nodes.add(blob) + + self._process_op(self._ops[blob]) + return self._nodes[blob] + + def _process_op(self, c2_op): + op_type = c2_op.type + args = self._parse_arg(c2_op.arg) + inputs = [self._get_node(i) for i in c2_op.input] + tvm_op = self._convert_operator(op_type, inputs, args) + + if not isinstance(tvm_op, _expr.TupleWrapper): + self._nodes[c2_op.output[0]] = tvm_op + else: + for k, i in zip(list(c2_op.output), range(len(tvm_op))): + self._nodes[k] = tvm_op[i] + + def _parse_arg(self, arg): + """Convert a list of Argument to a dict, with names as keys.""" + args = {} + for a in arg: + for f in ['f', 'i', 's']: + if a.HasField(f): + args[a.name] = getattr(a, f) + for f in ['floats', 'ints', 'strings']: + if list(getattr(a, f)): + assert a.name not in args, "Only one type of attr is allowed" + args[a.name] = tuple(getattr(a, f)) + for f in ['n']: + if a.HasField(f): + raise NotImplementedError( + "Field {} is not supported in relay.".format(f)) + for f in ['nets']: + if list(getattr(a, f)): + raise NotImplementedError( + "Field {} is not supported in relay.".format(f)) + if a.name not in args: + raise ValueError("Cannot parse attribute: \n{}\n.".format(a)) + return args + + def _convert_operator(self, + op_type, + inputs, + args, + identity_list=None, + convert_map=None): + """Convert from Caffe2 operator to Relay operator. + The converter must specify conversions explicity for incompatible name, and + apply handlers to operator attributes. + + Parameters + ---------- + op_type : str + Operator name, such as Convolution, FullyConnected + inputs : list of tvm.relay.expr.Function + List of input inputs. + args : dict + Dict of operator attributes + identity_list : list + List of operators that don't require conversion + convert_map : dict + Dict of name : callable, where name is the op's name that + require conversion to relay, callable are functions which + take args and return (new_op_type, new_args) + + Returns + ------- + func : tvm.relay.expr.Function + Converted relay function + """ + identity_list = identity_list if identity_list else _identity_list + convert_map = convert_map if convert_map else _get_convert_map() + if op_type in identity_list: + func = get_relay_op(op_type)(*inputs, **args) + elif op_type in convert_map: + # Add a sanitizing step to convert all byte strings in args to strings + func = convert_map[op_type](inputs, args, self._params) + else: + raise NotImplementedError( + "Operator {} not implemented.".format(op_type)) + return func + + +def from_caffe2(init_net, predict_net, shape=None, dtype="float32"): + """Load caffe2 graph which contains init_net and predict_net into Relay Function. + + Parameters + ---------- + init_net : protobuf object + Caffe2 NetDef containing the weights + + predict_net : protobuf object + Caffe2 NetDef containing the graph + + shape : dict of str to tuple + The input shape to the graph + + dtype : str or dict of str to str + The input types to the graph + + Returns + ------- + sym : tvm.relay.expr.Function + Compatible relay function + + params : dict of str to tvm.ndarray + Dict of converted parameters stored in tvm.ndarray format + """ + + caffe2 = Caffe2NetDef(shape, dtype) + return caffe2.from_caffe2(init_net, predict_net) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py new file mode 100644 index 000000000000..3f76896cc640 --- /dev/null +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -0,0 +1,23 @@ +"""Store for caffe2 examples and common models.""" +from __future__ import absolute_import as _abs +import os +import importlib +from . import squeezenet + +models = [ + 'squeezenet', + 'resnet50', + 'vgg19', +] + +# skip download if model exist +for model in models: + try: + locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) + except ImportError: + os.system("python -m caffe2.python.models.download -i -f " + model) + locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) + +# squeezenet +def relay_squeezenet(): + return squeezenet.get_workload() diff --git a/tests/python/frontend/caffe2/model_zoo/squeezenet.py b/tests/python/frontend/caffe2/model_zoo/squeezenet.py new file mode 100644 index 000000000000..74ade8989d05 --- /dev/null +++ b/tests/python/frontend/caffe2/model_zoo/squeezenet.py @@ -0,0 +1,132 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +# pylint: disable=unused-argument + +""" +Symbol of SqueezeNet + +Reference: +Iandola, Forrest N., et al. +"Squeezenet: Alexnet-level accuracy with 50x fewer parameters and< 0.5 mb model size." (2016). +""" + +from tvm import relay +from tvm.relay.testing import create_workload + +# Helpers +def _make_fire(net, squeeze_channels, expand1x1_channels, expand3x3_channels, prefix=""): + net = _make_fire_conv(net, squeeze_channels, 1, 0, "%s/squeeze1x1" % prefix) + + left = _make_fire_conv(net, expand1x1_channels, 1, 0, "%s/expand1x1" % prefix) + right = _make_fire_conv(net, expand3x3_channels, 3, 1, "%s/expand3x3" % prefix) + # NOTE : Assume NCHW layout here + net = relay.concatenate((left, right), axis=1) + return net + + +def _make_fire_conv(net, channels, kernel_size, padding=0, prefix=""): + net = relay.nn.conv2d(net, relay.var("%s_weight" % prefix), + channels=channels, + kernel_size=(kernel_size, kernel_size), + padding=(padding, padding)) + net = relay.nn.bias_add(net, relay.var("%s_bias" % prefix)) + net = relay.nn.relu(net) + return net + + +# Net +def get_net(batch_size, image_shape, num_classes, dtype): + """Get symbol of SqueezeNet + + Parameters + ---------- + batch_size : int + The batch size used in the model + + image_shape : tuple + The input image shape + + num_classes: int + The number of classification results + + dtype : str + The data type + + """ + data_shape = (batch_size,) + image_shape + net = relay.var("data", shape=data_shape, dtype=dtype) + net = relay.nn.conv2d(net, relay.var("conv1_weight"), + channels=64, + kernel_size=(3, 3), + strides=(2, 2), + padding=(0, 0)) + net = relay.nn.bias_add(net, relay.var("conv1_bias")) + net = relay.nn.relu(net) + net = relay.nn.max_pool2d(net, pool_size=(3, 3), strides=(2, 2)) + net = _make_fire(net, 16, 64, 64, 'fire2') + net = _make_fire(net, 16, 64, 64, "fire3") + net = relay.nn.max_pool2d(net, pool_size=(3, 3), strides=(2, 2)) + net = _make_fire(net, 32, 128, 128, "fire4") + net = _make_fire(net, 32, 128, 128, "fire5") + net = relay.nn.max_pool2d(net, pool_size=(3, 3), strides=(2, 2)) + net = _make_fire(net, 48, 192, 192, "fire6") + net = _make_fire(net, 48, 192, 192, "fire7") + net = _make_fire(net, 64, 256, 256, "fire8") + net = _make_fire(net, 64, 256, 256, "fire9") + net = relay.nn.dropout(net, rate=0.5) + net = relay.nn.conv2d(net, relay.var('conv10_weight'), channels=num_classes, kernel_size=(1, 1)) + net = relay.nn.bias_add(net, relay.var("conv10_bias")) + net = relay.nn.relu(net) + net = relay.nn.global_avg_pool2d(net) + net = relay.nn.softmax(net, axis=1) + args = relay.ir_pass.free_vars(net) + return relay.Function(args, net) + + +def get_workload(batch_size=1, + image_shape=(3, 224, 224), + num_classes=1000, + dtype="float32"): + """Get benchmark workload for SqueezeNet + + Parameters + ---------- + batch_size : int, optional + The batch size used in the model + + num_classes : int, optional + Number of classes + + image_shape : tuple, optional + The input image shape + + dtype : str, optional + The data type + + Returns + ------- + net : relay.Function + The computational graph + + params : dict of str to NDArray + The parameters. + """ + + net = get_net(batch_size, image_shape, num_classes, dtype) + return create_workload(net) diff --git a/tests/python/frontend/caffe2/test_forward.py b/tests/python/frontend/caffe2/test_forward.py new file mode 100644 index 000000000000..0f667339dcd8 --- /dev/null +++ b/tests/python/frontend/caffe2/test_forward.py @@ -0,0 +1,88 @@ +import numpy as np +import tvm +from tvm.contrib import graph_runtime +from tvm.relay.testing.config import ctx_list +from tvm import relay +from model_zoo import c2_squeezenet, c2_resnet50, c2_vgg19 +from caffe2.python import workspace + + +def get_tvm_output(model, + input_data, + target, + ctx, + output_shape, + output_dtype='float32'): + """ Generic function to execute and get tvm output""" + # supporting multiple inputs in caffe2 in a bit tricky, + # because the input names can appear at the beginning or end of model.predict_net.external_input + assert isinstance(input_data, np.ndarray) + + # here we use the first input blob to the first op to get the input name + input_names = model.predict_net.op[0].input[0] + shape_dict = {input_names: input_data.shape} + dtype_dict = {input_names: input_data.dtype} + func, params = relay.frontend.from_caffe2(model.init_net, model.predict_net, shape_dict, dtype_dict) + with relay.build_config(opt_level=3): + graph, lib, params = relay.build(func, target, params=params) + + ctx = tvm.cpu(0) + m = graph_runtime.create(graph, lib, ctx) + + # set inputs + m.set_input(input_names, tvm.nd.array(input_data.astype(input_data.dtype))) + m.set_input(**params) + + # execute + m.run() + + # get outputs + if isinstance(output_shape, list) and isinstance(output_dtype, list): + tvm_output_list = [] + for i, s in enumerate(output_shape): + tvm_output = m.get_output(i, tvm.nd.empty((s), output_dtype[i])) + tvm_output_list.append(tvm_output.asnumpy()) + return tvm_output_list + else: + tvm_output = m.get_output(0, tvm.nd.empty((output_shape), + output_dtype)) + return tvm_output.asnumpy() + + +def get_caffe2_output(model, x, dtype='float32'): + workspace.RunNetOnce(model.init_net) + + input_blob = model.predict_net.op[0].input[0] + workspace.FeedBlob(input_blob, x.astype(dtype)) + workspace.RunNetOnce(model.predict_net) + + output_blob = model.predict_net.external_output[0] + c2_output = workspace.FetchBlob(output_blob) + return c2_output + + +def verify_caffe2_forward_impl(model, data_shape, out_shape): + dtype = 'float32' + data = np.random.uniform(size=data_shape).astype(dtype) + c2_out = get_caffe2_output(model, data, dtype) + for target, ctx in ctx_list(): + tvm_out = get_tvm_output(model, data, target, ctx, out_shape, dtype) + tvm.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5) + + +def verify_squeezenet1_1(): + verify_caffe2_forward_impl(c2_squeezenet, (1, 3, 224, 224), (1, 1000, 1, 1)) + + +def verify_resnet50(): + verify_caffe2_forward_impl(c2_resnet50, (1, 3, 224, 224), (1, 1000)) + + +def verify_vgg19(): + verify_caffe2_forward_impl(c2_vgg19, (1, 3, 224, 224), (1, 1000)) + + +if __name__ == '__main__': + verify_squeezenet1_1() + verify_resnet50() + verify_vgg19() diff --git a/tests/python/frontend/caffe2/test_graph.py b/tests/python/frontend/caffe2/test_graph.py new file mode 100755 index 000000000000..ebcbf5b51770 --- /dev/null +++ b/tests/python/frontend/caffe2/test_graph.py @@ -0,0 +1,21 @@ +"""Test graph equality of caffe2 models.""" +from tvm import relay +from model_zoo import c2_squeezenet, relay_squeezenet + + +def compare_graph(f1, f2): + f1 = relay.ir_pass.infer_type(f1) + f2 = relay.ir_pass.infer_type(f2) + assert relay.ir_pass.alpha_equal(f1, f2) + + +def test_squeeze_net(): + shape_dict = {'data': (1, 3, 224, 224)} + dtype_dict = {'data': 'float32'} + from_c2_func, _ = relay.frontend.from_caffe2(c2_squeezenet.init_net, c2_squeezenet.predict_net, shape_dict, dtype_dict) + relay_func, _ = relay_squeezenet() + compare_graph(from_c2_func, relay_func) + + +if __name__ == '__main__': + test_squeeze_net() diff --git a/tests/scripts/task_python_frontend.sh b/tests/scripts/task_python_frontend.sh index beff2d47f464..204d9739c131 100755 --- a/tests/scripts/task_python_frontend.sh +++ b/tests/scripts/task_python_frontend.sh @@ -41,3 +41,7 @@ python3 -m nose -v tests/python/frontend/nnvm_to_relay || exit -1 echo "Running relay TFLite frontend test..." python3 -m nose -v tests/python/frontend/tflite || exit -1 + +echo "Running relay caffe2 frondend test..." +python3 -m nose -v tests/python/frontend/caffe2 || exit -1 + From c081677d7ecff3f448192c88071c35afeb253a4e Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 25 Jan 2019 15:34:04 +0900 Subject: [PATCH 02/15] [Relay][Frontend] Add Caffe2 Support (fix unsed import) --- python/tvm/relay/frontend/caffe2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tvm/relay/frontend/caffe2.py b/python/tvm/relay/frontend/caffe2.py index b0c68c9a4b05..69d3c3642cfe 100755 --- a/python/tvm/relay/frontend/caffe2.py +++ b/python/tvm/relay/frontend/caffe2.py @@ -1,7 +1,6 @@ # pylint: disable=import-self, invalid-name, line-too-long, unused-argument """Caffe2 frontend""" from __future__ import absolute_import as _abs -import tvm from .. import ir_pass from .. import expr as _expr from .. import op as _op From 07fcbf54a6d0f86333be6dd02bc447e9f26d0d30 Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 25 Jan 2019 17:41:30 +0900 Subject: [PATCH 03/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- tests/python/frontend/caffe2/model_zoo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index 3f76896cc640..db004cd032ae 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -15,7 +15,7 @@ try: locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except ImportError: - os.system("python -m caffe2.python.models.download -i -f " + model) + os.system("python3 -m caffe2.python.models.download -i -f " + model) locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) # squeezenet From 0d8b3541029a554d5b3bc0c0073b875646e7c971 Mon Sep 17 00:00:00 2001 From: makihiro Date: Tue, 29 Jan 2019 15:29:52 +0900 Subject: [PATCH 04/15] [Relay][Frontend] Add Caffe2 Support (fix model install and reflect code reviews) --- docker/Dockerfile.ci_gpu | 3 +++ docker/install/ubuntu_install_caffe2.sh | 3 +++ tests/python/frontend/caffe2/test_forward.py | 13 ++++++------- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 docker/install/ubuntu_install_caffe2.sh diff --git a/docker/Dockerfile.ci_gpu b/docker/Dockerfile.ci_gpu index fa15113289d0..6a599b1e3917 100644 --- a/docker/Dockerfile.ci_gpu +++ b/docker/Dockerfile.ci_gpu @@ -67,6 +67,9 @@ RUN bash /install/ubuntu_install_onnx.sh COPY install/ubuntu_install_tflite.sh /install/ubuntu_install_tflite.sh RUN bash /install/ubuntu_install_tflite.sh +COPY install/ubuntu_install_caffe2.sh /install/ubuntu_install_caffe2.sh +RUN bash /install/ubuntu_install_caffe2.sh + RUN pip3 install Pillow COPY install/ubuntu_install_vulkan.sh /install/ubuntu_install_vulkan.sh diff --git a/docker/install/ubuntu_install_caffe2.sh b/docker/install/ubuntu_install_caffe2.sh new file mode 100644 index 000000000000..5fe827927e87 --- /dev/null +++ b/docker/install/ubuntu_install_caffe2.sh @@ -0,0 +1,3 @@ +python3 -m caffe2.python.models.download -i -f squeezenet +python3 -m caffe2.python.models.download -i -f resnet50 +python3 -m caffe2.python.models.download -i -f vgg19 diff --git a/tests/python/frontend/caffe2/test_forward.py b/tests/python/frontend/caffe2/test_forward.py index 0f667339dcd8..9db57fe22bae 100644 --- a/tests/python/frontend/caffe2/test_forward.py +++ b/tests/python/frontend/caffe2/test_forward.py @@ -26,7 +26,6 @@ def get_tvm_output(model, with relay.build_config(opt_level=3): graph, lib, params = relay.build(func, target, params=params) - ctx = tvm.cpu(0) m = graph_runtime.create(graph, lib, ctx) # set inputs @@ -70,19 +69,19 @@ def verify_caffe2_forward_impl(model, data_shape, out_shape): tvm.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5) -def verify_squeezenet1_1(): +def test_verify_squeezenet1_1(): verify_caffe2_forward_impl(c2_squeezenet, (1, 3, 224, 224), (1, 1000, 1, 1)) -def verify_resnet50(): +def test_verify_resnet50(): verify_caffe2_forward_impl(c2_resnet50, (1, 3, 224, 224), (1, 1000)) -def verify_vgg19(): +def test_verify_vgg19(): verify_caffe2_forward_impl(c2_vgg19, (1, 3, 224, 224), (1, 1000)) if __name__ == '__main__': - verify_squeezenet1_1() - verify_resnet50() - verify_vgg19() + test_verify_squeezenet1_1() + test_verify_resnet50() + test_verify_vgg19() From 7ec284afcbe06b0415ca57c83e849412807d5e7b Mon Sep 17 00:00:00 2001 From: makihiro Date: Tue, 29 Jan 2019 19:24:20 +0900 Subject: [PATCH 05/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- .../frontend/caffe2/model_zoo/__init__.py | 45 ++++++++++++++++++- .../frontend/caffe2/model_zoo/__sym_init__.py | 20 +++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/python/frontend/caffe2/model_zoo/__sym_init__.py diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index db004cd032ae..dbbcaebd8e70 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -1,6 +1,7 @@ """Store for caffe2 examples and common models.""" from __future__ import absolute_import as _abs import os +import sys import importlib from . import squeezenet @@ -10,13 +11,53 @@ 'vgg19', ] +base_url = "https://s3.amazonaws.com/download.caffe2.ai/models" +# save the model data temporary for the test +model_base_dir = "/tmp" + +def _download(model, overwrite=False): + model_dir = '{folder}/{m}'.format(folder=model_base_dir, m=model) + if not os.path.isdir(model_dir): + os.makedirs(model_dir) + + for filename in ['predict_net.pb', 'init_net.pb']: + if os.path.isfile('{folder}/{f}'.format(folder=model_dir, f=filename)) and not overwrite: + return model_dir + try: + import urllib.request + urllib.request.urlretrieve('{url}/{m}/{f}'.format(url=base_url, m=model, f=filename), + '{folder}/{f}'.format(folder=model_dir, f=filename)) + except Exception: + import urllib + urllib.urlretrieve('{url}/{m}/{f}'.format(url=base_url, m=model, f=filename), + '{folder}/{f}'.format(folder=model_dir, f=filename)) + + os.symlink("{folder}/__sym_init__.py".format(folder=os.path.abspath(os.path.dirname(__file__))), + "{folder}/__init__.py".format(folder=model_dir)) + return model_dir + + + +def _as_abs_path(fname): + cur_dir = os.path.abspath(os.path.dirname(__file__)) + return os.path.join(cur_dir, fname) + + # skip download if model exist for model in models: try: + raise ImportError locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except ImportError: - os.system("python3 -m caffe2.python.models.download -i -f " + model) - locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) + try: + raise ModuleNotFoundError + os.system("python3 -m caffe2.python.models.download -i -f " + model) + locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) + except ModuleNotFoundError: + _download(model) + if model_base_dir not in sys.path: + sys.path.append(model_base_dir) + locals()['c2_' + model] = importlib.import_module(model) # squeezenet def relay_squeezenet(): diff --git a/tests/python/frontend/caffe2/model_zoo/__sym_init__.py b/tests/python/frontend/caffe2/model_zoo/__sym_init__.py new file mode 100644 index 000000000000..811b1052844c --- /dev/null +++ b/tests/python/frontend/caffe2/model_zoo/__sym_init__.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import os +from caffe2.proto import caffe2_pb2 + + +def _parseFile(filename): + out_net = caffe2_pb2.NetDef() + # TODO(bwasti): A more robust handler for pathnames. + dir_path = os.path.dirname(__file__) + with open('{dir_path}/{filename}'.format(dir_path=dir_path, + filename=filename), 'rb') as f: + out_net.ParseFromString(f.read()) + return out_net + + +init_net = _parseFile('init_net.pb') +predict_net = _parseFile('predict_net.pb') \ No newline at end of file From 32202cc55f0240ed70c829635b13b1cde88f8dad Mon Sep 17 00:00:00 2001 From: makihiro Date: Tue, 29 Jan 2019 19:26:30 +0900 Subject: [PATCH 06/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- tests/python/frontend/caffe2/model_zoo/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index dbbcaebd8e70..6893425a1f16 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -37,12 +37,6 @@ def _download(model, overwrite=False): return model_dir - -def _as_abs_path(fname): - cur_dir = os.path.abspath(os.path.dirname(__file__)) - return os.path.join(cur_dir, fname) - - # skip download if model exist for model in models: try: From 1ba933ed9ee8a65a23021373f07d26cadce9af74 Mon Sep 17 00:00:00 2001 From: makihiro Date: Tue, 29 Jan 2019 19:51:51 +0900 Subject: [PATCH 07/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- tests/python/frontend/caffe2/model_zoo/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index 6893425a1f16..0d32792c069e 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -40,11 +40,9 @@ def _download(model, overwrite=False): # skip download if model exist for model in models: try: - raise ImportError locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except ImportError: try: - raise ModuleNotFoundError os.system("python3 -m caffe2.python.models.download -i -f " + model) locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except ModuleNotFoundError: From 0ab0a69c3f72fe494aa920a0134821814e53558c Mon Sep 17 00:00:00 2001 From: makihiro Date: Tue, 29 Jan 2019 22:35:23 +0900 Subject: [PATCH 08/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 frontend import) --- python/tvm/relay/frontend/__init__.py | 1 + tests/python/frontend/caffe2/model_zoo/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/tvm/relay/frontend/__init__.py b/python/tvm/relay/frontend/__init__.py index 7b4fcd34af89..9d28a8802558 100644 --- a/python/tvm/relay/frontend/__init__.py +++ b/python/tvm/relay/frontend/__init__.py @@ -11,3 +11,4 @@ from .keras import from_keras from .onnx import from_onnx from .tflite import from_tflite +from .caffe2 import from_caffe2 diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index 0d32792c069e..1e0af32ed7f3 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -45,7 +45,7 @@ def _download(model, overwrite=False): try: os.system("python3 -m caffe2.python.models.download -i -f " + model) locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) - except ModuleNotFoundError: + except (PermissionError, ModuleNotFoundError): _download(model) if model_base_dir not in sys.path: sys.path.append(model_base_dir) From 765bd9d4e0652d01acde13c0dab447e1ee63d712 Mon Sep 17 00:00:00 2001 From: makihiro Date: Wed, 30 Jan 2019 10:01:20 +0900 Subject: [PATCH 09/15] [Relay][Frontend] Add Caffe2 Support (rename function name in test_forward) --- tests/python/frontend/caffe2/test_forward.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/python/frontend/caffe2/test_forward.py b/tests/python/frontend/caffe2/test_forward.py index 9db57fe22bae..655e9bc2bab5 100644 --- a/tests/python/frontend/caffe2/test_forward.py +++ b/tests/python/frontend/caffe2/test_forward.py @@ -69,19 +69,19 @@ def verify_caffe2_forward_impl(model, data_shape, out_shape): tvm.testing.assert_allclose(c2_out, tvm_out, rtol=1e-5, atol=1e-5) -def test_verify_squeezenet1_1(): +def test_forward_squeezenet1_1(): verify_caffe2_forward_impl(c2_squeezenet, (1, 3, 224, 224), (1, 1000, 1, 1)) -def test_verify_resnet50(): +def test_forward_resnet50(): verify_caffe2_forward_impl(c2_resnet50, (1, 3, 224, 224), (1, 1000)) -def test_verify_vgg19(): +def test_forward_vgg19(): verify_caffe2_forward_impl(c2_vgg19, (1, 3, 224, 224), (1, 1000)) if __name__ == '__main__': - test_verify_squeezenet1_1() - test_verify_resnet50() - test_verify_vgg19() + test_forward_squeezenet1_1() + test_forward_resnet50() + test_forward_vgg19() From 4cb77b4900b197022b5b0bc6350fb8431a074ccb Mon Sep 17 00:00:00 2001 From: makihiro Date: Wed, 30 Jan 2019 11:14:32 +0900 Subject: [PATCH 10/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- .../frontend/caffe2/model_zoo/__init__.py | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index 1e0af32ed7f3..ddf3e5f31fd5 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -4,6 +4,7 @@ import sys import importlib from . import squeezenet +from caffe2.python.models.download import ModelDownloader models = [ 'squeezenet', @@ -11,33 +12,12 @@ 'vgg19', ] -base_url = "https://s3.amazonaws.com/download.caffe2.ai/models" -# save the model data temporary for the test -model_base_dir = "/tmp" +mf = ModelDownloader() -def _download(model, overwrite=False): - model_dir = '{folder}/{m}'.format(folder=model_base_dir, m=model) - if not os.path.isdir(model_dir): - os.makedirs(model_dir) +class Model: + def __init__(self, model_name): + self.init_net, self.predict_net, self.value_info = mf.get_c2_model(model_name) - for filename in ['predict_net.pb', 'init_net.pb']: - if os.path.isfile('{folder}/{f}'.format(folder=model_dir, f=filename)) and not overwrite: - return model_dir - try: - import urllib.request - urllib.request.urlretrieve('{url}/{m}/{f}'.format(url=base_url, m=model, f=filename), - '{folder}/{f}'.format(folder=model_dir, f=filename)) - except Exception: - import urllib - urllib.urlretrieve('{url}/{m}/{f}'.format(url=base_url, m=model, f=filename), - '{folder}/{f}'.format(folder=model_dir, f=filename)) - - os.symlink("{folder}/__sym_init__.py".format(folder=os.path.abspath(os.path.dirname(__file__))), - "{folder}/__init__.py".format(folder=model_dir)) - return model_dir - - -# skip download if model exist for model in models: try: locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) @@ -46,10 +26,7 @@ def _download(model, overwrite=False): os.system("python3 -m caffe2.python.models.download -i -f " + model) locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except (PermissionError, ModuleNotFoundError): - _download(model) - if model_base_dir not in sys.path: - sys.path.append(model_base_dir) - locals()['c2_' + model] = importlib.import_module(model) + locals()['c2_' + model] = Model(model) # squeezenet def relay_squeezenet(): From 324eb62e7ea72ee7a117a2e9832499749065be55 Mon Sep 17 00:00:00 2001 From: makihiro Date: Wed, 30 Jan 2019 11:49:31 +0900 Subject: [PATCH 11/15] [Relay][Frontend] Add Caffe2 Support (fix caffe2 model import) --- tests/python/frontend/caffe2/model_zoo/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/python/frontend/caffe2/model_zoo/__init__.py b/tests/python/frontend/caffe2/model_zoo/__init__.py index ddf3e5f31fd5..18e74add8428 100644 --- a/tests/python/frontend/caffe2/model_zoo/__init__.py +++ b/tests/python/frontend/caffe2/model_zoo/__init__.py @@ -22,11 +22,7 @@ def __init__(self, model_name): try: locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) except ImportError: - try: - os.system("python3 -m caffe2.python.models.download -i -f " + model) - locals()['c2_' + model] = importlib.import_module('caffe2.python.models.' + model) - except (PermissionError, ModuleNotFoundError): - locals()['c2_' + model] = Model(model) + locals()['c2_' + model] = Model(model) # squeezenet def relay_squeezenet(): From b2c90a32a4584f1e12d2485b660037583aa82ce2 Mon Sep 17 00:00:00 2001 From: makihiro Date: Thu, 31 Jan 2019 16:52:08 +0900 Subject: [PATCH 12/15] [Doc] Caffe2 frontend tutorial --- tutorials/frontend/from_caffe2.py | 115 ++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tutorials/frontend/from_caffe2.py diff --git a/tutorials/frontend/from_caffe2.py b/tutorials/frontend/from_caffe2.py new file mode 100644 index 000000000000..dbbc10612934 --- /dev/null +++ b/tutorials/frontend/from_caffe2.py @@ -0,0 +1,115 @@ +""" +Compile Caffe2 Models +===================== +**Author**: `Hiroyuki Makino `_ + +This article is an introductory tutorial to deploy Caffe2 models with Relay. + +For us to begin with, Caffe2 should be installed. + +A quick solution is to install via pip + +.. code-block:: bash + + + +or please refer to official site +https://caffe2.ai/docs/getting-started.html +""" +import tvm +from tvm import relay +import numpy as np + +def download(url, path, overwrite=False): + import os + if os.path.isfile(path) and not overwrite: + print('File {} exists, skip.'.format(path)) + return + print('Downloading from url {} to {}'.format(url, path)) + try: + import urllib.request + urllib.request.urlretrieve(url, path) + except: + import urllib + urllib.urlretrieve(url, path) + +###################################################################### +# Load pretrained caffe2 model +# ---------------------------- +# We load a pretrained resnet50 classification model provided by caffe2. +from caffe2.python.models.download import ModelDownloader +mf = ModelDownloader() + +class Model: + def __init__(self, model_name): + self.init_net, self.predict_net, self.value_info = mf.get_c2_model(model_name) + +resnet50 = Model('resnet50') + + +###################################################################### +# Load a test image +# ------------------ +# A single cat dominates the examples! +from PIL import Image +from matplotlib import pyplot as plt +from keras.applications.resnet50 import preprocess_input +img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true' +download(img_url, 'cat.png') +img = Image.open('cat.png').resize((224, 224)) +plt.imshow(img) +plt.show() +# input preprocess +data = np.array(img)[np.newaxis, :].astype('float32') +data = preprocess_input(data).transpose([0, 3, 1, 2]) +print('input_1', data.shape) + +###################################################################### +# Compile the model on Relay +# -------------------------- +# We should be familiar with the process now. + +# convert the caffe2 model(NHWC layout) to relay functions. +target = 'cuda' +shape_dict = {'input_1': data.shape} +dtype_dict = {'input_1': data.dtype} +func, params = relay.frontend.from_caffe2(resnet50.init_net, resnet50.predict_net, shape_dict, dtype_dict) +# compile the model +with relay.build_config(opt_level=3): + graph, lib, params = relay.build(func, target, params=params) + +###################################################################### +# Execute on TVM +# --------------- +# The process is no different from other examples. +from tvm.contrib import graph_runtime +ctx = tvm.gpu(0) +m = graph_runtime.create(graph, lib, ctx) +# set inputs +m.set_input('input_1', tvm.nd.array(data.astype('float32'))) +m.set_input(**params) +# execute +m.run() +# get outputs +tvm_out = m.get_output(0) +top1_tvm = np.argmax(tvm_out.asnumpy()[0]) + +##################################################################### +# Look up synset name +# ------------------- +# Look up prediction top 1 index in 1000 class synset. +from caffe2.python import workspace +synset_url = ''.join(['https://gist.githubusercontent.com/zhreshold/', + '4d0b62f3d01426887599d4f7ede23ee5/raw/', + '596b27d23537e5a1b5751d2b0481ef172f58b539/', + 'imagenet1000_clsid_to_human.txt']) +synset_name = 'synset.txt' +download(synset_url, synset_name) +with open(synset_name) as f: + synset = eval(f.read()) +print('Relay top-1 id: {}, class name: {}'.format(top1_tvm, synset[top1_tvm])) +# confirm correctness with caffe2 output +p = workspace.Predictor(resnet50.init_net, resnet50.predict_net) +caffe2_out = p.run({'data': data.transpose([0, 2, 3, 1])}) +top1_caffe2 = np.argmax(caffe2_out) +print('Caffe2 top-1 id: {}, class name: {}'.format(top1_caffe2, synset[top1_caffe2])) From 871c39e36b9edaf9155f0453d3db9f27655dce9f Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 1 Feb 2019 12:15:10 +0900 Subject: [PATCH 13/15] [Doc] Caffe2 frontend tutorial --- tutorials/frontend/from_caffe2.py | 46 +++++++++++++++++++------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/tutorials/frontend/from_caffe2.py b/tutorials/frontend/from_caffe2.py index dbbc10612934..064d1ffa03a9 100644 --- a/tutorials/frontend/from_caffe2.py +++ b/tutorials/frontend/from_caffe2.py @@ -7,19 +7,21 @@ For us to begin with, Caffe2 should be installed. -A quick solution is to install via pip +A quick solution is to install via conda .. code-block:: bash - + # for cpu + conda install pytorch-nightly-cpu -c pytorch + # for gpu with CUDA 8 + conda install pytorch-nightly cuda80 -c pytorch or please refer to official site https://caffe2.ai/docs/getting-started.html """ -import tvm -from tvm import relay -import numpy as np - +###################################################################### +# Utils for downloading files +# ---------------------------- def download(url, path, overwrite=False): import os if os.path.isfile(path) and not overwrite: @@ -34,9 +36,9 @@ def download(url, path, overwrite=False): urllib.urlretrieve(url, path) ###################################################################### -# Load pretrained caffe2 model +# Load pretrained Caffe2 model # ---------------------------- -# We load a pretrained resnet50 classification model provided by caffe2. +# We load a pretrained resnet50 classification model provided by Caffe2. from caffe2.python.models.download import ModelDownloader mf = ModelDownloader() @@ -46,7 +48,6 @@ def __init__(self, model_name): resnet50 = Model('resnet50') - ###################################################################### # Load a test image # ------------------ @@ -54,6 +55,7 @@ def __init__(self, model_name): from PIL import Image from matplotlib import pyplot as plt from keras.applications.resnet50 import preprocess_input +import numpy as np img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true' download(img_url, 'cat.png') img = Image.open('cat.png').resize((224, 224)) @@ -62,19 +64,23 @@ def __init__(self, model_name): # input preprocess data = np.array(img)[np.newaxis, :].astype('float32') data = preprocess_input(data).transpose([0, 3, 1, 2]) -print('input_1', data.shape) ###################################################################### # Compile the model on Relay # -------------------------- -# We should be familiar with the process now. -# convert the caffe2 model(NHWC layout) to relay functions. -target = 'cuda' -shape_dict = {'input_1': data.shape} -dtype_dict = {'input_1': data.dtype} +# Caffe2 input tensor name, shape and type +input_name = resnet50.predict_net.op[0].input[0] +shape_dict = {input_name: data.shape} +dtype_dict = {input_name: data.dtype} + +# parse Caffe2 model and convert into Relay computation graph +from tvm import relay func, params = relay.frontend.from_caffe2(resnet50.init_net, resnet50.predict_net, shape_dict, dtype_dict) + # compile the model +# target x86 cpu +target = 'llvm' with relay.build_config(opt_level=3): graph, lib, params = relay.build(func, target, params=params) @@ -82,11 +88,15 @@ def __init__(self, model_name): # Execute on TVM # --------------- # The process is no different from other examples. +import tvm from tvm.contrib import graph_runtime -ctx = tvm.gpu(0) +# context x86 cpu, use tvm.gpu(0) if you run on GPU +ctx = tvm.cpu(0) +# create a runtime executor module m = graph_runtime.create(graph, lib, ctx) # set inputs -m.set_input('input_1', tvm.nd.array(data.astype('float32'))) +m.set_input(input_name, tvm.nd.array(data.astype('float32'))) +# set related params m.set_input(**params) # execute m.run() @@ -110,6 +120,6 @@ def __init__(self, model_name): print('Relay top-1 id: {}, class name: {}'.format(top1_tvm, synset[top1_tvm])) # confirm correctness with caffe2 output p = workspace.Predictor(resnet50.init_net, resnet50.predict_net) -caffe2_out = p.run({'data': data.transpose([0, 2, 3, 1])}) +caffe2_out = p.run({input_name: data}) top1_caffe2 = np.argmax(caffe2_out) print('Caffe2 top-1 id: {}, class name: {}'.format(top1_caffe2, synset[top1_caffe2])) From 30bdc9d83d2790695787b5f49e7d025733f6c278 Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 1 Feb 2019 14:55:56 +0900 Subject: [PATCH 14/15] [Doc] Caffe2 frontend tutorial --- tutorials/frontend/from_caffe2.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tutorials/frontend/from_caffe2.py b/tutorials/frontend/from_caffe2.py index 064d1ffa03a9..fce7f30d865d 100644 --- a/tutorials/frontend/from_caffe2.py +++ b/tutorials/frontend/from_caffe2.py @@ -54,7 +54,6 @@ def __init__(self, model_name): # A single cat dominates the examples! from PIL import Image from matplotlib import pyplot as plt -from keras.applications.resnet50 import preprocess_input import numpy as np img_url = 'https://github.com/dmlc/mxnet.js/blob/master/data/cat.png?raw=true' download(img_url, 'cat.png') @@ -62,8 +61,14 @@ def __init__(self, model_name): plt.imshow(img) plt.show() # input preprocess -data = np.array(img)[np.newaxis, :].astype('float32') -data = preprocess_input(data).transpose([0, 3, 1, 2]) +def transform_image(image): + image = np.array(image) - np.array([123., 117., 104.]) + image /= np.array([58.395, 57.12, 57.375]) + image = image.transpose((2, 0, 1)) + image = image[np.newaxis, :].astype('float32') + return image + +data = transform_image(img) ###################################################################### # Compile the model on Relay From ea664fa90047b2c3df2cff06ef472d0d0f24e45b Mon Sep 17 00:00:00 2001 From: makihiro Date: Fri, 1 Feb 2019 15:04:57 +0900 Subject: [PATCH 15/15] [Relay][Frontend] Add Caffe2 Support (remove unsed file) --- .../frontend/caffe2/model_zoo/__sym_init__.py | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 tests/python/frontend/caffe2/model_zoo/__sym_init__.py diff --git a/tests/python/frontend/caffe2/model_zoo/__sym_init__.py b/tests/python/frontend/caffe2/model_zoo/__sym_init__.py deleted file mode 100644 index 811b1052844c..000000000000 --- a/tests/python/frontend/caffe2/model_zoo/__sym_init__.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals -import os -from caffe2.proto import caffe2_pb2 - - -def _parseFile(filename): - out_net = caffe2_pb2.NetDef() - # TODO(bwasti): A more robust handler for pathnames. - dir_path = os.path.dirname(__file__) - with open('{dir_path}/{filename}'.format(dir_path=dir_path, - filename=filename), 'rb') as f: - out_net.ParseFromString(f.read()) - return out_net - - -init_net = _parseFile('init_net.pb') -predict_net = _parseFile('predict_net.pb') \ No newline at end of file