From 94abb4f86f81d483f39a60b0a36314a637ab501e Mon Sep 17 00:00:00 2001 From: Prashant Sharma Date: Mon, 8 Nov 2021 17:32:50 +0530 Subject: [PATCH] Support upto 4D array as REST input payload. --- .gitignore | 3 + proxy/marshaler.go | 18 ++++- proxy/n-dim-marshaler.go | 108 +++++++++++++++++++++++++++++ proxy/n-dim-marshaler_test.go | 123 ++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 proxy/n-dim-marshaler.go create mode 100644 proxy/n-dim-marshaler_test.go diff --git a/.gitignore b/.gitignore index 1e16531..3a8674b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ google/ # general .env .bash_history + +# IDE +.idea/ diff --git a/proxy/marshaler.go b/proxy/marshaler.go index 6acd0e0..809516d 100644 --- a/proxy/marshaler.go +++ b/proxy/marshaler.go @@ -162,7 +162,6 @@ func (c *CustomJSONPb) NewDecoder(r io.Reader) runtime.Decoder { req.Inputs = make([]*gw.ModelInferRequest_InferInputTensor, 0, len(restReq.Inputs)) // TODO: Figure out better/cleaner way to do type coercion? - // TODO: Flatten N-Dimensional data arrays. for index, input := range restReq.Inputs { tensor := &gw.ModelInferRequest_InferInputTensor{ @@ -172,6 +171,23 @@ func (c *CustomJSONPb) NewDecoder(r io.Reader) runtime.Decoder { Parameters: input.Parameters, } d := input.Data.([]interface{}) + var err error + if len(d) != int(elementCount(tensor.Shape)) { // i.e. input is not already flattened. + switch len(tensor.Shape) { + case 2: + d, err = flatten2D(input) + case 3: + d, err = flatten3D(input) + case 4: + d, err = flatten4D(input) + default: + return fmt.Errorf("Unsupported dimension %d, maximum supported dimension is 4.", + len(tensor.Shape)) + } + if err != nil { + return err + } + } switch tensor.Datatype { case BOOL: data := make([]bool, len(d)) diff --git a/proxy/n-dim-marshaler.go b/proxy/n-dim-marshaler.go new file mode 100644 index 0000000..5f0876b --- /dev/null +++ b/proxy/n-dim-marshaler.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "fmt" + "strings" +) + +func flatten2D(tensor Tensor) ([]interface{}, error) { + totalLen := int(elementCount(tensor.Shape)) + inputData := tensor.Data.([]interface{}) + flattenedOutputData := make([]interface{}, totalLen) + // we need to flatten the array into 1D + var arrayDeserialized []float64 + var arrayBool []bool + var err error + n1, n2 := int(tensor.Shape[0]), int(tensor.Shape[1]) + for j := 0; j < n1; j++ { + str := fmt.Sprintf("%v", inputData[j]) + str = strings.Replace(str, " ", ", ", -1) // convert it to a valid json + if tensor.Datatype == BOOL { + err = json.Unmarshal([]byte(str), &arrayBool) + } else { + err = json.Unmarshal([]byte(str), &arrayDeserialized) + } + if err != nil { + return nil, fmt.Errorf("found error while deserializing the 2D array, with shape: %v. Error: %s", + tensor.Shape, err.Error()) + } + for z := 0; z < n2; z++ { // using row major order to flatten a 2D array. + if tensor.Datatype == BOOL { + flattenedOutputData[z+(j*n2)] = arrayBool[z] + } else { + flattenedOutputData[z+(j*n2)] = arrayDeserialized[z] + } + } + } + return flattenedOutputData, nil +} + +func flatten3D(tensor Tensor) ([]interface{}, error) { + totalLen := int(elementCount(tensor.Shape)) + inputData := tensor.Data.([]interface{}) + flattenedOutputData := make([]interface{}, totalLen) + var arrayDeserialized [][]float64 + var arrayBool [][]bool + var err error + n1, n2, n3 := int(tensor.Shape[0]), int(tensor.Shape[1]), int(tensor.Shape[2]) + for j := 0; j < n1; j++ { + str := fmt.Sprintf("%v", inputData[j]) + str = strings.Replace(str, " ", ", ", -1) + if tensor.Datatype == BOOL { + err = json.Unmarshal([]byte(str), &arrayBool) + } else { + err = json.Unmarshal([]byte(str), &arrayDeserialized) + } + if err != nil { + return nil, fmt.Errorf("found error while deserializing the 3D array, with shape: %v. Error: %s", + tensor.Shape, err.Error()) + } + for z := 0; z < n2; z++ { + for k := 0; k < n3; k++ { + if tensor.Datatype == BOOL { + flattenedOutputData[k+j*n2*n3+z*n3] = arrayBool[z][k] + } else { + flattenedOutputData[k+j*n2*n3+z*n3] = arrayDeserialized[z][k] + } + } + } + } + return flattenedOutputData, nil +} + +func flatten4D(tensor Tensor) ([]interface{}, error) { + totalLen := int(elementCount(tensor.Shape)) + inputData := tensor.Data.([]interface{}) + flattenedOutputData := make([]interface{}, totalLen) + var arrayDeserialized [][][]float64 + var arrayBool [][][]bool + var err error + n1, n2, n3, n4 := int(tensor.Shape[0]), int(tensor.Shape[1]), int(tensor.Shape[2]), int(tensor.Shape[3]) + for j := 0; j < n1; j++ { + str := fmt.Sprintf("%v", inputData[j]) + str = strings.Replace(str, " ", ", ", -1) // convert to a valid json + if tensor.Datatype == BOOL { + err = json.Unmarshal([]byte(str), &arrayBool) + } else { + err = json.Unmarshal([]byte(str), &arrayDeserialized) + + } + if err != nil { + return nil, fmt.Errorf("found error while deserializing the 4D array, with shape: %v. Error: %s", + tensor.Shape, err.Error()) + } + for z := 0; z < n2; z++ { + for k := 0; k < n3; k++ { + for l := 0; l < n4; l++ { + if tensor.Datatype == BOOL { + flattenedOutputData[k*n4+j*n2*n3*n4+z*n3*n4+l] = arrayBool[z][k][l] + } else { + flattenedOutputData[k*n4+j*n2*n3*n4+z*n3*n4+l] = arrayDeserialized[z][k][l] + } + } + } + } + } + return flattenedOutputData, nil +} diff --git a/proxy/n-dim-marshaler_test.go b/proxy/n-dim-marshaler_test.go new file mode 100644 index 0000000..c5a44c3 --- /dev/null +++ b/proxy/n-dim-marshaler_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "github.com/google/go-cmp/cmp" + "testing" +) + +var inputTensor2D = Tensor{ + Name: "test_tensor2d", + Datatype: "FP32", + Shape: []int64{2, 64}, + Parameters: nil, + Data: []interface{}{ + []interface{}{0.0, 0.0, 1.0, 11.0, 14.0, 15.0, 3.0, 0.0, 0.0, 1.0, 13.0, 16.0, 12.0, 16.0, 8.0, 0.0, 0.0, 8.0, + 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, 16.0, 13.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, + 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + []interface{}{2.0, 1.0, 4.0, 9.0, 8.0, 16.0, 2.0, 7.0, 9.0, 1.0, 12.0, 16.0, 12.0, 16.0, 8.0, 0.0, 0.0, 8.0, + 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, 16.0, 13.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, + 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + }, +} + +var inputTensor3D = Tensor{ + Name: "test_tensor3d", + Datatype: "FP32", + Shape: []int64{2, 2, 32}, + Parameters: nil, + Data: []interface{}{ + []interface{}{ + []interface{}{0.0, 0.0, 1.0, 11.0, 14.0, 15.0, 3.0, 0.0, 0.0, 1.0, 13.0, 16.0, 12.0, 16.0, 8.0, 0.0, 0.0, + 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0}, + []interface{}{0.0, 0.0, 2.0, 12.0, 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, + 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + }, + []interface{}{ + []interface{}{2.0, 1.0, 4.0, 9.0, 8.0, 16.0, 2.0, 7.0, 9.0, 1.0, 12.0, 16.0, 12.0, 16.0, 8.0, 0.0, 0.0, + 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0}, + []interface{}{0.0, 0.0, 2.0, 12.0, 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, + 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + }, + }, +} + +var inputTensor4D = Tensor{ + Name: "test_tensor4d", + Datatype: "FP32", + Shape: []int64{2, 2, 2, 16}, + Parameters: nil, + Data: []interface{}{ + []interface{}{ + []interface{}{ + []interface{}{0.0, 0.0, 1.0, 11.0, 14.0, 15.0, 3.0, 0.0, 0.0, 1.0, 13.0, 16.0, 12.0, 16.0, 8.0, 0.0}, + []interface{}{0.0, 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0}, + }, + []interface{}{ + []interface{}{0.0, 0.0, 2.0, 12.0, 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0}, + []interface{}{0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + }, + }, + []interface{}{ + []interface{}{ + []interface{}{2.0, 1.0, 4.0, 9.0, 8.0, 16.0, 2.0, 7.0, 9.0, 1.0, 12.0, 16.0, 12.0, 16.0, 8.0, 0.0}, + []interface{}{0.0, 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0}, + }, + []interface{}{ + []interface{}{0.0, 0.0, 2.0, 12.0, 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0}, + []interface{}{0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0}, + }, + }, + }, +} + +var expectedOutput = []interface{}{0.0, 0.0, 1.0, 11.0, 14.0, 15.0, 3.0, 0.0, 0.0, 1.0, 13.0, 16.0, 12.0, 16.0, 8.0, + 0.0, 0.0, 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, + 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, + 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0, 2.0, 1.0, 4.0, 9.0, 8.0, 16.0, 2.0, 7.0, 9.0, 1.0, 12.0, 16.0, 12.0, 16.0, + 8.0, 0.0, 0.0, 8.0, 16.0, 4.0, 6.0, 16.0, 5.0, 0.0, 0.0, 5.0, 15.0, 11.0, 13.0, 14.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, + 16.0, 13.0, 0.0, 0.0, 0.0, 0.0, 0.0, 13.0, 16.0, 16.0, 6.0, 0.0, 0.0, 0.0, 0.0, 16.0, 16.0, 16.0, 7.0, 0.0, 0.0, + 0.0, 0.0, 11.0, 13.0, 12.0, 1.0, 0.0} + +func Test_flatten2D(t *testing.T) { + data, err := flatten2D(inputTensor2D) + if err != nil { + t.Errorf("Failed to parse 2D array. %s", err.Error()) + } + if len(data) != int(elementCount(inputTensor2D.Shape)) { + t.Errorf("conversion failed, output array should have length: %d", + int(elementCount(inputTensor2D.Shape))) + } + if d := cmp.Diff(data, expectedOutput); d != "" { + t.Errorf("diff: %v", d) + } +} + +func Test_flatten3D(t *testing.T) { + data, err := flatten3D(inputTensor3D) + if err != nil { + t.Errorf("Failed to parse 3D array. %s", err.Error()) + } + if len(data) != int(elementCount(inputTensor3D.Shape)) { + t.Errorf("conversion failed, output array should have length: %d", + int(elementCount(inputTensor3D.Shape))) + } + if d := cmp.Diff(data, expectedOutput); d != "" { + t.Errorf("diff: %v", d) + } +} + +func Test_flatten4D(t *testing.T) { + data, err := flatten4D(inputTensor4D) + if err != nil { + t.Errorf("Failed to parse 4D array. %s", err.Error()) + } + if len(data) != int(elementCount(inputTensor4D.Shape)) { + t.Errorf("conversion failed, output array should have length: %d", + int(elementCount(inputTensor4D.Shape))) + } + if d := cmp.Diff(data, expectedOutput); d != "" { + t.Errorf("diff: %v", d) + } +}