fullstorydev / grpcurl

Like cURL, but for gRPC: Command-line tool for interacting with gRPC servers
MIT License
10.83k stars 507 forks source link

JSON representation of Tensorflow Predict protobuf message float array not working #194

Closed adriangay closed 3 years ago

adriangay commented 4 years ago

Trying to get grpcurl to send a predict request to Tensorflow Serving. If I use protobuf text representation, I get a successful prediction result. But I've been unable to create a JSON representation that works. In fact the only way I managed to get the text version to work was by using google.protobuf.text_format() to dump a Predict request message to a file then cat it into stdin. The issue seems to be in serialising the tensor float array from JSON . Several different type representations are provided, but they all result is an incorrect tensor shape being reported by the Tensorflow model. Maybe its a serialisation and packing issue with the JSON field?

The Predict message proto is:

syntax = "proto3";

package tensorflow.serving;
option cc_enable_arenas = true;

import "tensorflow/core/framework/tensor.proto";
import "tensorflow_serving/apis/model.proto";

// PredictRequest specifies which TensorFlow model to run, as well as
// how inputs are mapped to tensors and how outputs are filtered before
// returning to user.
message PredictRequest {
  // Model Specification. If version is not specified, will use the latest
  // (numerical) version.
  ModelSpec model_spec = 1;

  // Input tensors.
  // Names of input tensor are alias names. The mapping from aliases to real
  // input tensor names is stored in the SavedModel export as a prediction
  // SignatureDef under the 'inputs' field.
  map<string, TensorProto> inputs = 2;

  // Output filter.
  // Names specified are alias names. The mapping from aliases to real output
  // tensor names is stored in the SavedModel export as a prediction
  // SignatureDef under the 'outputs' field.
  // Only tensors specified here will be run/fetched and returned, with the
  // exception that when none is specified, all tensors specified in the
  // named signature will be run/fetched and returned.
  repeated string output_filter = 3;
}

// Response for PredictRequest on successful run.
message PredictResponse {
  // Effective Model Specification used to process PredictRequest.
  ModelSpec model_spec = 2;

  // Output tensors.
  map<string, TensorProto> outputs = 1;
}

As you can see below, the TensorProto spec offers several ways of representing the tensor 'array' as repeated types:

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 = "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_go_proto";

// 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;
}

Here is an example using Protobuf text syntax, produced by google.protobuf.text_format() and written direct to file. I have no control over which type it chose to use, in this case tensor_content. Sending this with grpcurl with -format=text works:

model_spec {
  name: "tagging"
  signature_name: "serving_default"
}
inputs {
  key: "input_1"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 252
      }
    }
    tensor_content: "\000\210\001F\000\000\200?\000\000\032C\000\000vD\000\000\200?\000\350<F\000H\213E\000\310lF\000@GE\000\250\031F\000\330\266E\000\000\222B\000\000\331C\000\000\222B\000\0200E\000\240\002F\000\000vC\000\200QD\000\300jE\000\324aF\000\000`B\000pkE\000\000\337C\000\200\204C\000h\231E\000`\035E\000\254cG\000p\035E\000H[F\000\200\331C\000\000\264C\000\000PA\000\000\006C\000`\254E\000\300nE\000@\036D\000\000vC\000\200QD\000\000\250A\000\200\001D\000$,F\0000,E\000\350\273E\000 uE\000\230\342E\0000\221E\000\300\271E\000\000\320C\000DSG\000\310\236E\000\000\303C\0000NF\000\300\023D\000\230\264E\000\2005E\000\000\001C\000pkE\000\000\337C\000\200\204C\000h\231E\000`\035E\000\254cG\000p\035E\000H[F\000\200\331C\000\000\264C\000\000PA\000\000\006C\000`\254E\000\300nE\000@\036D\000\000vC\000\200QD\000\000\250A\000\200\001D\000$,F\0000,E\000\350\273E\000 uE\000\230\342E\0000\221E\000\300\271E\000\000\320C\000DSG\000\310\236E\000\000\303C\0000NF\000\300\023D\000\230\264E\000\2005E\000\000\001C\000\200\225C\000@AD\000\000\337C\000\260\331E\000\000\314C\000\300PD\000 \227D\000\000\306C\000h\231E\000`\035E\000\254cG\000p\035E\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
  }
}

As a human, I want to represent the Tensor float array in a form it's easy to specify, unlike the above. The following protobuf text form also works:

model_spec {
  name: "tagging"
  signature_name: "serving_default"
}
inputs {
  key: "input_1"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 252
      }
    }
    float_val: [8.2900e+3, 1.0, 1.5400e+2, 9.8400e+2, 1.0,
        1.2090e+4, 4.4570e+3, 1.5154e+4, 3.1880e+3, 9.8340e+3,
        5.8510e+3, 7.3000e+1, 4.3400e+2, 7.3000e+1, 2.8170e+3,
        8.3600e+3, 2.4600e+2, 8.3800e+2, 3.7560e+3, 1.4453e+4,
        5.6000e+1, 3.7670e+3, 4.4600e+2, 2.6500e+2, 4.9090e+3,
        2.5180e+3, 5.8284e+4, 2.5190e+3, 1.4034e+4, 4.3500e+2,
        3.6000e+2, 1.3000e+1, 1.3400e+2, 5.5160e+3, 3.8200e+3,
        6.3300e+2, 2.4600e+2, 8.3800e+2, 2.1000e+1, 5.1800e+2,
        1.1017e+4, 2.7550e+3, 6.0130e+3, 3.9220e+3, 7.2510e+3,
        4.6460e+3, 5.9440e+3, 4.1600e+2, 5.4084e+4, 5.0810e+3,
        3.9000e+2, 1.3196e+4, 5.9100e+2, 5.7790e+3, 2.9040e+3,
        1.2900e+2, 3.7670e+3, 4.4600e+2, 2.6500e+2, 4.9090e+3,
        2.5180e+3, 5.8284e+4, 2.5190e+3, 1.4034e+4, 4.3500e+2,
        3.6000e+2, 1.3000e+1, 1.3400e+2, 5.5160e+3, 3.8200e+3,
        6.3300e+2, 2.4600e+2, 8.3800e+2, 2.1000e+1, 5.1800e+2,
        1.1017e+4, 2.7550e+3, 6.0130e+3, 3.9220e+3, 7.2510e+3,
        4.6460e+3, 5.9440e+3, 4.1600e+2, 5.4084e+4, 5.0810e+3,
        3.9000e+2, 1.3196e+4, 5.9100e+2, 5.7790e+3, 2.9040e+3,
        1.2900e+2, 2.9900e+2, 7.7300e+2, 4.4600e+2, 6.9660e+3,
        4.0800e+2, 8.3500e+2, 1.2090e+3, 3.9600e+2, 4.9090e+3,
        2.5180e+3, 5.8284e+4, 2.5190e+3, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0, 0.0,
        0.0, 0.0]  
  }            
}

Aside: I found it interesting how protobuf represents a map in the text form.

Here is a JSON example of the above that passes the grpcurl syntax checking and a request is sent using -format=json, but Tensorflow complains about the tensor shape, suggesting the repeated float array is not being serialised as expected:

{
    "model_spec": {
        "name": "tagging",
        "signature_name": "serving_default"
    },
    "inputs": {
        "input_1": 
        {
            "dtype": "DT_FLOAT",
            "tensor_shape": [
                {"dim": {
                    "size": 1
                }},
                {"dim": {
                    "size": 252
                }}
            ],
            "float_val": [
                8.2900e+3, 1.0, 1.5400e+2, 9.8400e+2, 1.0,
                1.2090e+4, 4.4570e+3, 1.5154e+4, 3.1880e+3, 9.8340e+3,
                5.8510e+3, 7.3000e+1, 4.3400e+2, 7.3000e+1, 2.8170e+3,
                8.3600e+3, 2.4600e+2, 8.3800e+2, 3.7560e+3, 1.4453e+4,
                5.6000e+1, 3.7670e+3, 4.4600e+2, 2.6500e+2, 4.9090e+3,
                2.5180e+3, 5.8284e+4, 2.5190e+3, 1.4034e+4, 4.3500e+2,
                3.6000e+2, 1.3000e+1, 1.3400e+2, 5.5160e+3, 3.8200e+3,
                6.3300e+2, 2.4600e+2, 8.3800e+2, 2.1000e+1, 5.1800e+2,
                1.1017e+4, 2.7550e+3, 6.0130e+3, 3.9220e+3, 7.2510e+3,
                4.6460e+3, 5.9440e+3, 4.1600e+2, 5.4084e+4, 5.0810e+3,
                3.9000e+2, 1.3196e+4, 5.9100e+2, 5.7790e+3, 2.9040e+3,
                1.2900e+2, 3.7670e+3, 4.4600e+2, 2.6500e+2, 4.9090e+3,
                2.5180e+3, 5.8284e+4, 2.5190e+3, 1.4034e+4, 4.3500e+2,
                3.6000e+2, 1.3000e+1, 1.3400e+2, 5.5160e+3, 3.8200e+3,
                6.3300e+2, 2.4600e+2, 8.3800e+2, 2.1000e+1, 5.1800e+2,
                1.1017e+4, 2.7550e+3, 6.0130e+3, 3.9220e+3, 7.2510e+3,
                4.6460e+3, 5.9440e+3, 4.1600e+2, 5.4084e+4, 5.0810e+3,
                3.9000e+2, 1.3196e+4, 5.9100e+2, 5.7790e+3, 2.9040e+3,
                1.2900e+2, 2.9900e+2, 7.7300e+2, 4.4600e+2, 6.9660e+3,
                4.0800e+2, 8.3500e+2, 1.2090e+3, 3.9600e+2, 4.9090e+3,
                2.5180e+3, 5.8284e+4, 2.5190e+3, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0, 0.0, 0.0, 0.0,
                0.0, 0.0
            ]
        }               
    }
}

Please help me understand if/how I can make this work with JSON, preferably using JSON float representation as in the example above. Thanks.

For info, I also tried a bytes -> base64 representation, which is equivalent, but results in the same, underlying serialisation problem:

"string_val": "AMyT/IN/AAA="
jhump commented 4 years ago

Hi, @adriangay, as you've correctly guessed, you can pass an array of numbers as float_val or a base64-encoded string as string_val. That's not the problem.

but Tensorflow complains about the tensor shape

I am pretty sure your problem is the shape, not the contents.

I assume the TensorShapeProto you are trying to use is this one.

Your example has this:

"tensor_shape": [
    {"dim": {
        "size": 1
    }},
    {"dim": {
        "size": 252
    }}
],

But the tensor_shape field is not repeated, so it should not be an array. And the dim field therein is repeated, so it should be an array. So I think you've got the array vs. object relationship backwards. You want this instead:

"tensor_shape": {
    "dim": [
        {"size": 1},
        {"size": 252}
    ]
},
adriangay commented 3 years ago

@jhump Thank you so much for this. I have been staring at this for far too long and was looking at the Tensor array data for the issue. I feel so dumb! :) Yes, that was it.