From 33d4454dfe1604ea2713edce43e55869acfa8a13 Mon Sep 17 00:00:00 2001 From: liuqi Date: Tue, 5 Dec 2017 17:39:03 +0800 Subject: [PATCH] Add gcn validation tools. --- mace/examples/mace_run.cc | 2 + mace/kernels/opencl/fused_conv_2d_opencl.cc | 33 ++--- mace/python/tools/tf_converter.py | 2 +- mace/python/tools/tf_converter_lib.py | 36 +++++- tools/validate.py | 126 ++++++++++++++++++++ tools/validate_gcn.sh | 77 ++++++++++++ 6 files changed, 256 insertions(+), 20 deletions(-) create mode 100644 tools/validate.py create mode 100644 tools/validate_gcn.sh diff --git a/mace/examples/mace_run.cc b/mace/examples/mace_run.cc index 21eade9c..0905477e 100644 --- a/mace/examples/mace_run.cc +++ b/mace/examples/mace_run.cc @@ -100,8 +100,10 @@ int main(int argc, char **argv) { in_file.close(); } + VLOG(0) << "Before Init"; // Init model auto net = CreateNet(net_def, &ws, device_type, NetMode::INIT); + VLOG(0) << "Before Run"; net->Run(); // run model diff --git a/mace/kernels/opencl/fused_conv_2d_opencl.cc b/mace/kernels/opencl/fused_conv_2d_opencl.cc index 8e75cb9d..9e23c57f 100644 --- a/mace/kernels/opencl/fused_conv_2d_opencl.cc +++ b/mace/kernels/opencl/fused_conv_2d_opencl.cc @@ -28,6 +28,11 @@ extern void Conv2dOpenclK3x3S2(const Tensor *input, const Tensor *filter, const int *padding, const DataType dt, Tensor *output); +extern void Conv2dOpencl(const Tensor *input, const Tensor *filter, + const Tensor *bias, const bool fused_relu, + const uint32_t stride, const int *padding, + const DataType dt, Tensor *output); + template void FusedConv2dFunctor::operator()(const Tensor *input, const Tensor *filter, @@ -44,20 +49,15 @@ void FusedConv2dFunctor::operator()(const Tensor *input, {Conv2dOpenclK3x3S1, Conv2dOpenclK3x3S2}, {nullptr, nullptr}, {nullptr, nullptr}}; - index_t kernel_h = filter->dim(0); index_t kernel_w = filter->dim(1); - if (kernel_h != kernel_w || kernel_h > 5 || strides_[0] != strides_[1] || - strides_[0] > 2 || dilations_[0] != 1 || dilations_[1] != 1 || - selector[kernel_h - 1][strides_[0] - 1] == nullptr) { + if (!input->is_image() || strides_[0] != strides_[1] || + strides_[0] > 2 || dilations_[0] != 1 || dilations_[1] != 1) { LOG(WARNING) << "OpenCL conv2d kernel with " << "filter" << kernel_h << "x" << kernel_w << "," << " stride " << strides_[0] << "x" << strides_[1] << " is not implemented yet, using slow version"; - // TODO(heliangliang) The CPU/NEON kernel should map the buffer - FusedConv2dFunctor(strides_, paddings_, dilations_)( - input, filter, bias, output); - return; + MACE_NOT_IMPLEMENTED; } std::vector output_shape(4); @@ -66,16 +66,17 @@ void FusedConv2dFunctor::operator()(const Tensor *input, input->shape().data(), filter->shape().data(), dilations_, strides_, paddings_, output_shape.data(), paddings.data()); - if (input->is_image()) { - std::vector output_image_shape; - CalImage2DShape(output_shape, BufferType::IN_OUT, output_image_shape); - output->ResizeImage(output_shape, output_image_shape); + std::vector output_image_shape; + CalImage2DShape(output_shape, BufferType::IN_OUT, output_image_shape); + output->ResizeImage(output_shape, output_image_shape); + + if (kernel_h == kernel_w && kernel_h <= 5 && + selector[kernel_h - 1][strides_[0] - 1] != nullptr) { + auto conv2d_func = selector[kernel_h - 1][strides_[0] - 1]; + conv2d_func(input, filter, bias, false, paddings.data(), DataTypeToEnum::value, output); } else { - output->Resize(output_shape); + Conv2dOpencl(input, filter, bias, false, strides_[0], paddings.data(), DataTypeToEnum::value, output); } - - auto conv2d_func = selector[kernel_h - 1][strides_[0] - 1]; - conv2d_func(input, filter, bias, true, paddings.data(), DataTypeToEnum::value, output); } template diff --git a/mace/python/tools/tf_converter.py b/mace/python/tools/tf_converter.py index 8f1cbf66..c1792f44 100644 --- a/mace/python/tools/tf_converter.py +++ b/mace/python/tools/tf_converter.py @@ -24,7 +24,7 @@ def main(unused_args): input_graph_def, FLAGS.input_node, FLAGS.output_node, FLAGS.prequantize) else: output_graph_def = tf_converter_lib.convert_to_mace_pb( - input_graph_def, FLAGS.runtime) + input_graph_def, FLAGS.input_node, FLAGS.output_node, FLAGS.runtime) with gfile.GFile(FLAGS.output, "wb") as f: f.write(output_graph_def.SerializeToString()) diff --git a/mace/python/tools/tf_converter_lib.py b/mace/python/tools/tf_converter_lib.py index 39ec8a6e..37fd539b 100644 --- a/mace/python/tools/tf_converter_lib.py +++ b/mace/python/tools/tf_converter_lib.py @@ -45,9 +45,11 @@ def get_input_tensor(op, index): def add_buffer_to_image(input_name, input_type, net_def): output_name = input_name[:-2] + "_b2i" + input_name[-2:] op_def = net_def.op.add() - op_def.name = output_name + op_def.name = output_name[:-2] op_def.type = 'BufferToImage' op_def.input.extend([input_name]) + op_def.output.extend([output_name]) + epsilon_arg = op_def.arg.add() epsilon_arg.name = 'buffer_type' epsilon_arg.i = buffer_type_map[input_type] @@ -56,6 +58,30 @@ def add_buffer_to_image(input_name, input_type, net_def): epsilon_arg.i = 0 return output_name +def add_input_transform(name, net_def): + new_input_name = "mace_input_node:0" + op_def = net_def.op.add() + op_def.name = name + op_def.type = 'BufferToImage' + op_def.input.extend([new_input_name]) + op_def.output.extend([name+':0']) + + epsilon_arg = op_def.arg.add() + epsilon_arg.name = 'buffer_type' + epsilon_arg.i = buffer_type_map['IN_OUT'] + +def add_output_transform(name, net_def): + output_name = "mace_output_node:0" + op_def = net_def.op.add() + op_def.name = output_name[:-2] + op_def.type = 'ImageToBuffer' + op_def.input.extend([name+':0']) + op_def.output.extend([output_name]) + + epsilon_arg = op_def.arg.add() + epsilon_arg.name = 'buffer_type' + epsilon_arg.i = buffer_type_map['IN_OUT'] + def convert_ops(unresolved_ops, net_def, device): ops_count = len(unresolved_ops) resolved_count = 1 @@ -257,7 +283,7 @@ def convert_ops(unresolved_ops, net_def, device): size_arg.ints.extend(get_input_tensor(first_op, 1).eval().astype(np.int32).flat) size_arg = op_def.arg.add() size_arg.name = 'align_corners' - size_arg.ints.extend(first_op.get_attr('align_corners')) + size_arg.i = first_op.get_attr('align_corners') output_shapes = [] for output in first_op.outputs: output_shape = mace_pb2.OutputShape() @@ -284,7 +310,7 @@ def convert_ops(unresolved_ops, net_def, device): del unresolved_ops[0] -def convert_to_mace_pb(input_graph_def, device): +def convert_to_mace_pb(input_graph_def, input_node, output_node, device): net_def = mace_pb2.NetDef() with tf.Session() as session: @@ -292,8 +318,12 @@ def convert_to_mace_pb(input_graph_def, device): tf.import_graph_def(input_graph_def, name="") ops = graph.get_operations() unresolved_ops = ops + if device == 'gpu': + add_input_transform(input_node, net_def) while len(unresolved_ops) > 0: convert_ops(unresolved_ops, net_def, device) + if device == 'gpu': + add_output_transform(output_node, net_def) print "PB Parsed." diff --git a/tools/validate.py b/tools/validate.py new file mode 100644 index 00000000..f70c59f3 --- /dev/null +++ b/tools/validate.py @@ -0,0 +1,126 @@ +import argparse +import sys +import tensorflow as tf +import numpy as np + +from tensorflow import gfile + +# Validation Flow: +# 1. Generate input data +# python validate_icnet.py --generate_data 1 \ +# --random_seed 1 +# 2. Use mace_run to run icnet on phone. +# 3. adb pull the result. +# 4. Compare output data of mace and tf +# python validate_icnet.py --model_file opt_icnet.pb \ +# --input_file input_file \ +# --mace_out_file icnet.out + + +def generate_data(shape): + np.random.seed(FLAGS.random_seed) + data = np.random.random(shape) + print FLAGS.input_file + data.astype(np.float32).tofile(FLAGS.input_file) + print "Generate input file done." + +def load_data(file): + return np.fromfile(file=file, dtype=np.float32) + +def valid_output(out_shape, mace_out_file, tf_out_value): + mace_out_value = load_data(mace_out_file) + mace_out_value = mace_out_value.reshape(out_shape) + res = np.allclose(tf_out_value, mace_out_value, rtol=0, atol=1e-5) + print 'Passed! Haha' if res else 'Failed! Oops' + + +def run_model(input_shape): + if not gfile.Exists(FLAGS.model_file): + print("Input graph file '" + FLAGS.model_file + "' does not exist!") + return -1 + + input_graph_def = tf.GraphDef() + with gfile.Open(FLAGS.model_file, "rb") as f: + data = f.read() + input_graph_def.ParseFromString(data) + tf.import_graph_def(input_graph_def, name="") + + with tf.Session() as session: + with session.graph.as_default() as graph: + tf.import_graph_def(input_graph_def, name="") + input_node = graph.get_tensor_by_name(FLAGS.input_node + ':0') + output_node = graph.get_tensor_by_name(FLAGS.output_node + ':0') + + input_value = load_data(FLAGS.input_file) + input_value = input_value.reshape(input_shape) + + output_value = session.run(output_node, feed_dict={input_node: [input_value]}) + return output_value + +def main(unused_args): + input_shape = [int(x) for x in FLAGS.input_shape.split(',')] + output_shape = [int(x) for x in FLAGS.output_shape.split(',')] + if FLAGS.generate_data: + generate_data(input_shape) + else: + output_value = run_model(input_shape) + valid_output(output_shape, FLAGS.mace_out_file, output_value) + + +def parse_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser() + parser.register("type", "bool", lambda v: v.lower() == "true") + parser.add_argument( + "--model_file", + type=str, + default="", + help="TensorFlow \'GraphDef\' file to load.") + parser.add_argument( + "--input_file", + type=str, + default="", + help="input file.") + parser.add_argument( + "--mace_out_file", + type=str, + default="", + help="mace output file to load.") + parser.add_argument( + "--input_shape", + type=str, + default="512,512,3", + help="input shape.") + parser.add_argument( + "--output_shape", + type=str, + default="1,512,512,2", + help="output shape.") + parser.add_argument( + "--input_node", + type=str, + default="input_node", + help="input node") + parser.add_argument( + "--output_node", + type=str, + default="output_node", + help="output node") + parser.add_argument( + "--generate_data", + type='bool', + default="false", + help="Random seed for generate test case.") + parser.add_argument( + "--random_seed", + type=int, + default="0", + help="Random seed for generate test case.") + + return parser.parse_known_args() + + +if __name__ == '__main__': + FLAGS, unparsed = parse_args() + main(unused_args=[sys.argv[0]] + unparsed) + diff --git a/tools/validate_gcn.sh b/tools/validate_gcn.sh new file mode 100644 index 00000000..279f1165 --- /dev/null +++ b/tools/validate_gcn.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Must run at root dir of mace project. +set -e + +Usage() { + echo 'Usage: bash tools/validate_gcn.sh tf_model_file' +} + +if [ $# != 1 ];then + Usage + exit -1 +fi + +TF_MODEL_FILE_PATH=$1 +MODEL_DIR=$(dirname ${TF_MODEL_FILE_PATH}) +MACE_MODEL_NAME='mace_model.pb' +INPUT_FILE_NAME='model_input' +OUTPUT_FILE_NAME='gcn.out' +PHONE_DATA_DIR="/data/local/tmp/${MACE_MODEL_NAME}" +KERNEL_DIR="${PHONE_DATA_DIR}/cl/" + +# Step 1: convert tf model to mace model +#echo "Step 1: convert tf model to mace model" +#bazel build //mace/python/tools:tf_converter +#bazel-bin/mace/python/tools/tf_converter --input=${TF_MODEL_FILE_PATH} \ +# --output=${MODEL_DIR}/${MACE_MODEL_NAME} \ +# --input_node=input \ +# --output_node=GCN/br_result_2/fcn_br \ +# --runtime=gpu +# +## Step 2: Generate input data +#echo "Step 2: Generate input data" +#python tools/validate.py --generate_data true --random_seed 1 \ +# --input_file=${MODEL_DIR}/${INPUT_FILE_NAME} \ +# --input_shape=512,512,3 + +# Step 3: Run model on the phone +echo "Step 3: Run model on the phone" +bazel build -c opt --strip always mace/examples:mace_run \ + --crosstool_top=//external:android/crosstool \ + --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ + --cpu=arm64-v8a + +adb shell "mkdir -p ${PHONE_DATA_DIR}" +adb shell "mkdir -p ${KERNEL_DIR}" +adb push mace/kernels/opencl/cl/* ${KERNEL_DIR} +adb push ${MODEL_DIR}/${MACE_MODEL_NAME} ${PHONE_DATA_DIR} +adb push ${MODEL_DIR}/${INPUT_FILE_NAME} ${PHONE_DATA_DIR} +adb push bazel-bin/mace/examples/mace_run ${PHONE_DATA_DIR} + +num_threads=${1:-1} + +adb shell MACE_RUN_PARAMETER_PATH=${PHONE_DATA_DIR}/mace_run.config \ + MACE_KERNEL_PATH=$KERNEL_DIR \ + OMP_NUM_THREADS=$num_threads \ + ${PHONE_DATA_DIR}/mace_run \ + --model=${PHONE_DATA_DIR}/${MACE_MODEL_NAME} \ + --input=mace_input_node \ + --output=mace_output_node \ + --input_shape=1,512,512,3\ + --input_file=${PHONE_DATA_DIR}/${MACE_INPUT_FILE_NAME} \ + --output_file=${PHONE_DATA_DIR}/${OUTPUT_FILE_NAME} \ + --device=OPENCL + +# Step 4: pull the mace run result. +echo "Step 4: pull the mace run result." +adb pull ${PHONE_DATA_DIR}/${OUTPUT_FILE_NAME} ${MODEL_DIR} + +# Step 5: validate the result +echo "Step 5: validate the result" +python tools/validate.py --model_file ${TF_MODEL_FILE_PATH} \ + --input_file ${MODEL_DIR}/${INPUT_FILE_NAME} \ + --mace_out_file ${MODEL_DIR}/${OUTPUT_FILE_NAME} \ + --input_node input \ + --output_node GCN/br_result_2/fcn_br + + -- GitLab