Skip to content

Commit

Permalink
Initial Go client sample (openvinotoolkit#1014)
Browse files Browse the repository at this point in the history
  • Loading branch information
mzegla authored Nov 15, 2021
1 parent 3b9f4f4 commit ead15b3
Show file tree
Hide file tree
Showing 13 changed files with 1,778 additions and 1 deletion.
50 changes: 50 additions & 0 deletions example_client/go/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#
# Copyright (c) 2021 Intel Corporation
#
# Licensed 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.
#

FROM golang:latest
ARG http_proxy

RUN echo "Acquire::http::Proxy \"$http_proxy\";" > /etc/apt/apt.conf.d/proxy.conf
RUN apt-get update && \
apt-get -y install git unzip build-essential autoconf libtool protobuf-compiler libprotobuf-dev
RUN go get google.golang.org/grpc
RUN go get github.com/golang/protobuf/protoc-gen-go

# Install Go OpenCV (to simplify postprocessing)
RUN apt-get install -y sudo && \
git clone https://github.com/hybridgroup/gocv.git && \
cd gocv && \
make install

RUN mkdir /app
COPY . /app
WORKDIR /app

# Compile API
RUN protoc -I apis/ apis/tensorflow_serving/apis/*.proto --go_out=plugins=grpc:.
RUN protoc -I apis/ apis/tensorflow/core/framework/*.proto --go_out=plugins=grpc:.

# Move compiled protos under GOROOT
RUN mv tensorflow /usr/local/go/src/
RUN mv tensorflow_serving /usr/local/go/src/

## we run go build to compile the binary
RUN go mod init ovmsclient
RUN go mod tidy
RUN go build .


ENTRYPOINT ["/app/ovmsclient"]
61 changes: 61 additions & 0 deletions example_client/go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Go client for prediction

This client has been created to demostrate how to interact with OpenVINO Model Server prediction endpoint from Go application. Presented example demonstrates end to end flow for running classification on JPG/PNG image using ResNet50 model. For simplicity of the environment setup, the example is run in the Docker container.


## Get the model

To run end to end flow and get correct results, please download `resnet-50-tf` model and convert it to IR format by following [instructions available on the OpenVINO Model Zoo page](https://docs.openvinotoolkit.org/latest/omz_models_model_resnet_50_tf.html)

Place converted model files (XML and BIN) under the following path: `<PATH_TO_MODELS>/resnet/1`

Where `PATH_TO_MODELS` is the path to the directory with models on the host filesystem.

For example:
```
/home/user/models/resnet/1/resnet-50-tf.xml
/home/user/models/resnet/1/resnet-50-tf.bin
```

## Build Go client docker image

To build the docker image and tag it `ovmsclient` run:
```
docker build . -t ovmsclient
```

## Start OpenVINO Model Server with ResNet model

Before running the client launch OVMS with prepared ResNet model. You can do that with a command similar to:

```
docker run -d --rm -p 9000:9000 -v <PATH_TO_MODELS>/resnet:/models/resnet openvino/model-server:latest --model_name resnet --model_path /models/resnet --port 9000 --layout NHWC
```

**Note** Changing the layout with `--layout NHWC` option is necessary in this example, so the model will accept binary input generated by the client. See [binary inputs](../../docs/binary_input.md) doc if you want to learn more about this feature.

## Run prediction with Go client

In order to run prediction on the model served by the OVMS using Go client run the following command:

`docker run --net=host --rm ovmsclient --serving-address localhost:9000 zebra.jpeg`

Command explained:
- `--net=host` option is required so the container with the client can access container with the model server via host network (localhost),
- `--serving-address` parameter defines the address of the model server gRPC endpoint,
- the last part in the command is a path to the image that will be send to OVMS for prediction. The image must be accessible from the inside of the container (could be mounted). Single zebra picture - `zebra.jpeg` - has been embedded in the docker image to simplify the example, so above command would work out of the box. If you wish to use other image you need to provide it to the container and change the path.

You can also choose if the image should be sent as binary input (raw JPG or PNG bytes) or should be converted on the client side to the data array accepted by the model.
To send raw bytes just add `--binary-input` flag like this:

`docker run --net=host --rm ovmsclient --serving-address localhost:9000 --binary-input zebra.jpeg`

### Exemplary output:

If the client successfully prepared and sent the request and then received a valid response, the output of this command should look somewhat like this:
```
$ docker run --net=host --rm ovmsclient --serving-address localhost:9000 zebra.jpeg
2021/08/30 15:46:40 Request sent successfully
Predicted class: zebra
Classification confidence: 98.353996%
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
syntax = "proto3";

package tensorflow;

import "tensorflow/core/framework/tensor_shape.proto";
import "tensorflow/core/framework/types.proto";

option cc_enable_arenas = true;
option java_outer_classname = "ResourceHandle";
option java_multiple_files = true;
option java_package = "org.tensorflow.framework";
option go_package = "tensorflow/core/framework";

// Protocol buffer representing a handle to a tensorflow resource. Handles are
// not valid across executions, but can be serialized back and forth from within
// a single run.
message ResourceHandleProto {
// Unique name for the device containing the resource.
string device = 1;

// Container in which this resource is placed.
string container = 2;

// Unique name of this resource.
string name = 3;

// Hash code for the type of the resource. Is only valid in the same device
// and in the same execution.
uint64 hash_code = 4;

// For debug-only, the name of the type pointed to by this handle, if
// available.
string maybe_type_name = 5;

// Protocol buffer representing a pair of (data type, tensor shape).
message DtypeAndShape {
DataType dtype = 1;
TensorShapeProto shape = 2;
}

// Data types and shapes for the underlying resource.
repeated DtypeAndShape dtypes_and_shapes = 6;

reserved 7;
}
96 changes: 96 additions & 0 deletions example_client/go/apis/tensorflow/core/framework/tensor.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
syntax = "proto3";

package tensorflow;

import "tensorflow/core/framework/resource_handle.proto";
import "tensorflow/core/framework/tensor_shape.proto";
import "tensorflow/core/framework/types.proto";

option cc_enable_arenas = true;
option java_outer_classname = "TensorProtos";
option java_multiple_files = true;
option java_package = "org.tensorflow.framework";
option go_package = "tensorflow/core/framework";

// Protocol buffer representing a tensor.
message TensorProto {
DataType dtype = 1;

// Shape of the tensor. TODO(touts): sort out the 0-rank issues.
TensorShapeProto tensor_shape = 2;

// Only one of the representations below is set, one of "tensor_contents" and
// the "xxx_val" attributes. We are not using oneof because as oneofs cannot
// contain repeated fields it would require another extra set of messages.

// Version number.
//
// In version 0, if the "repeated xxx" representations contain only one
// element, that element is repeated to fill the shape. This makes it easy
// to represent a constant Tensor with a single value.
int32 version_number = 3;

// Serialized raw tensor content from either Tensor::AsProtoTensorContent or
// memcpy in tensorflow::grpc::EncodeTensorToByteBuffer. This representation
// can be used for all tensor types. The purpose of this representation is to
// reduce serialization overhead during RPC call by avoiding serialization of
// many repeated small items.
bytes tensor_content = 4;

// Type specific representations that make it easy to create tensor protos in
// all languages. Only the representation corresponding to "dtype" can
// be set. The values hold the flattened representation of the tensor in
// row major order.

// DT_HALF, DT_BFLOAT16. Note that since protobuf has no int16 type, we'll
// have some pointless zero padding for each value here.
repeated int32 half_val = 13 [packed = true];

// DT_FLOAT.
repeated float float_val = 5 [packed = true];

// DT_DOUBLE.
repeated double double_val = 6 [packed = true];

// DT_INT32, DT_INT16, DT_INT8, DT_UINT8.
repeated int32 int_val = 7 [packed = true];

// DT_STRING
repeated bytes string_val = 8;

// DT_COMPLEX64. scomplex_val(2*i) and scomplex_val(2*i+1) are real
// and imaginary parts of i-th single precision complex.
repeated float scomplex_val = 9 [packed = true];

// DT_INT64
repeated int64 int64_val = 10 [packed = true];

// DT_BOOL
repeated bool bool_val = 11 [packed = true];

// DT_COMPLEX128. dcomplex_val(2*i) and dcomplex_val(2*i+1) are real
// and imaginary parts of i-th double precision complex.
repeated double dcomplex_val = 12 [packed = true];

// DT_RESOURCE
repeated ResourceHandleProto resource_handle_val = 14;

// DT_VARIANT
repeated VariantTensorDataProto variant_val = 15;

// DT_UINT32
repeated uint32 uint32_val = 16 [packed = true];

// DT_UINT64
repeated uint64 uint64_val = 17 [packed = true];
}

// Protocol buffer representing the serialization format of DT_VARIANT tensors.
message VariantTensorDataProto {
// Name of the type of objects being serialized.
string type_name = 1;
// Portions of the object that are not Tensors.
bytes metadata = 2;
// Tensors contained within objects being serialized.
repeated TensorProto tensors = 3;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Protocol buffer representing the shape of tensors.

syntax = "proto3";
option cc_enable_arenas = true;
option java_outer_classname = "TensorShapeProtos";
option java_multiple_files = true;
option java_package = "org.tensorflow.framework";
option go_package = "tensorflow/core/framework";

package tensorflow;

// Dimensions of a tensor.
message TensorShapeProto {
// One dimension of the tensor.
message Dim {
// Size of the tensor in that dimension.
// This value must be >= -1, but values of -1 are reserved for "unknown"
// shapes (values of -1 mean "unknown" dimension). Certain wrappers
// that work with TensorShapeProto may fail at runtime when deserializing
// a TensorShapeProto containing a dim value of -1.
int64 size = 1;

// Optional name of the tensor dimension.
string name = 2;
};

// Dimensions of the tensor, such as {"input", 30}, {"output", 40}
// for a 30 x 40 2D tensor. If an entry has size -1, this
// corresponds to a dimension of unknown size. The names are
// optional.
//
// The order of entries in "dim" matters: It indicates the layout of the
// values in the tensor in-memory representation.
//
// The first entry in "dim" is the outermost dimension used to layout the
// values, the last entry is the innermost dimension. This matches the
// in-memory layout of RowMajor Eigen tensors.
//
// If "dim.size()" > 0, "unknown_rank" must be false.
repeated Dim dim = 2;

// If true, the number of dimensions in the shape is unknown.
//
// If true, "dim.size()" must be 0.
bool unknown_rank = 3;
};
89 changes: 89 additions & 0 deletions example_client/go/apis/tensorflow/core/framework/types.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
syntax = "proto3";

package tensorflow;
option cc_enable_arenas = true;
option java_outer_classname = "TypesProtos";
option java_multiple_files = true;
option java_package = "org.tensorflow.framework";
option go_package = "tensorflow/core/framework";

// (== suppress_warning documentation-presence ==)
// LINT.IfChange
enum DataType {
// Not a legal value for DataType. Used to indicate a DataType field
// has not been set.
DT_INVALID = 0;

// Data types that all computation devices are expected to be
// capable to support.
DT_FLOAT = 1;
DT_DOUBLE = 2;
DT_INT32 = 3;
DT_UINT8 = 4;
DT_INT16 = 5;
DT_INT8 = 6;
DT_STRING = 7;
DT_COMPLEX64 = 8; // Single-precision complex
DT_INT64 = 9;
DT_BOOL = 10;
DT_QINT8 = 11; // Quantized int8
DT_QUINT8 = 12; // Quantized uint8
DT_QINT32 = 13; // Quantized int32
DT_BFLOAT16 = 14; // Float32 truncated to 16 bits. Only for cast ops.
DT_QINT16 = 15; // Quantized int16
DT_QUINT16 = 16; // Quantized uint16
DT_UINT16 = 17;
DT_COMPLEX128 = 18; // Double-precision complex
DT_HALF = 19;
DT_RESOURCE = 20;
DT_VARIANT = 21; // Arbitrary C++ data types
DT_UINT32 = 22;
DT_UINT64 = 23;

// Do not use! These are only for parameters. Every enum above
// should have a corresponding value below (verified by types_test).
DT_FLOAT_REF = 101;
DT_DOUBLE_REF = 102;
DT_INT32_REF = 103;
DT_UINT8_REF = 104;
DT_INT16_REF = 105;
DT_INT8_REF = 106;
DT_STRING_REF = 107;
DT_COMPLEX64_REF = 108;
DT_INT64_REF = 109;
DT_BOOL_REF = 110;
DT_QINT8_REF = 111;
DT_QUINT8_REF = 112;
DT_QINT32_REF = 113;
DT_BFLOAT16_REF = 114;
DT_QINT16_REF = 115;
DT_QUINT16_REF = 116;
DT_UINT16_REF = 117;
DT_COMPLEX128_REF = 118;
DT_HALF_REF = 119;
DT_RESOURCE_REF = 120;
DT_VARIANT_REF = 121;
DT_UINT32_REF = 122;
DT_UINT64_REF = 123;
}
// LINT.ThenChange(
// https://www.tensorflow.org/code/tensorflow/c/tf_datatype.h,
// https://www.tensorflow.org/code/tensorflow/go/tensor.go,
// https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc,
// https://www.tensorflow.org/code/tensorflow/core/framework/types.h,
// https://www.tensorflow.org/code/tensorflow/core/framework/types.cc,
// https://www.tensorflow.org/code/tensorflow/python/framework/dtypes.py,
// https://www.tensorflow.org/code/tensorflow/python/framework/function.py)

// For identifying the underlying type of a variant. For variants, the types
// listed here are a subset of the types in the variant type registry,
// corresponding to commonly used variants which must occasionally be
// special-cased.
enum SpecializedType {
// Invalid/unknown specialized type.
ST_INVALID = 0;
// "tensorflow::TensorList" in the variant type registry.
ST_TENSOR_LIST = 1;
// "tensorflow::data::Optional" in the variant type registry.
ST_OPTIONAL = 2;
}
Loading

0 comments on commit ead15b3

Please sign in to comment.