TrustyAI-KServe conversions

Synopsis

Inference data is represented in TrustyAI1 using objects implementing the Prediction interface. The objects always include two general entities representing collections of inputs (PredictionInput) and outputs (PredictionOutput). These in turn are comprised of Features and Outputs, respectively.

TrustyAI predictions.excalidraw.svg

Communication with KServe/ModelMesh deployed models is done, however, using gRPC, with the KServe v2 protocol.

TrustyAI KServe.excalidraw.svg

The basis of prediction requests is the KServe ModelInferenceRequest message, where model responses are based on the ModelInferenceResponse message. Payloads (and data in general) are encoded as InferInputTensor and InferResponseTensor. Here we look at several scenarios to convert data to/from KServe tensors and TrustyAI Predictions.

Single input

np codec

Currently, the conversion between PredictionInput and PredictionOutput to KServe Tensor format works in the following way: We assume a consistent datatype between the features and serialise it as a $(1, N_f)$ shaped tensor.

Note that although a JSON representation of the data is used, it is only for illustration of the structure. The TrustyAI structures are converted to Protobuf format.

KServe TrustyAI schema 1.excalidraw.svg

This is using the default KServe conversion codec np.

pd codec

KServe also allows to use the pd conversion codec, where each feature is a $(1, 1)$ shaped Tensor. This allows for greater flexibility, especially in the common case where each feature has a different datatype. In TrustyAI, this conversion works the following way:

KServe TrustyAI schema 2.excalidraw.svg

Outputs

For the single input case, the output conversion is straightforward. We get a $(1, N_f)$ shaped tensor for the outputs, which allows for direct conversion to a single PredictionOutput, such that

KServe TrustyAI schema 3.excalidraw.svg

Multiple inputs

There are basically two ways of dealing with the multiple inputs, such as in the single inputs case. In this case we will consider $N_{obs}$ inputs, each with $N_f$ features.

np codec

With the np codec, we simply produce a $(N_{obs}, N_f)$ shaped tensor, sharing the same datatype.

KServe TrustyAI schema 4.excalidraw.svg

pd codec

KServe TrustyAI schema 5.excalidraw.svg

Outputs

With multiple outputs come multiple outputs and here the situation is not as straightforward. Let’s assume that for the previous inputs we get two features as the inference result. Usually, the outputs will come as a $(1, N_{obs}\times N_f)$ shaped tensor, so we will need to reshape it. So we’ll have something like

{ 
	"model_name": "dummy3", 
	"model_version": "v0.0.1", 
	"id": "3aacc8a0-a3cc-47ce-bd03-614df833d18e", 
	"parameters": {}, 
	"outputs": [ 
		{ 
			"name": "predict", 
			"shape": [ 3, 2 ], 
			"datatype": "INT64", 
			"data": [ 2, 2, 1, 4, 2, 2 ] 
		} 
	] 
}

Since we know the final shape of the multiple outputs (with the shape value), we can reshape it using:

KServe TrustyAI schema 6.excalidraw.svg

Dataframe utilities

A collection of Prediction can be also represented as a Dataframe. Dataframe will represent the data in tabular format along with the necessary metadata related to the feature types, feature domains and if the feature belongs in the inputs or outputs.

TrustyAI dataframe.excalidraw.svg
A new data structure, called a TensorDataframe is an extension of the Dataframe, sharing a common API, but adding a few convenience methods to convert either individual rows (or the entire data) to tensors. The first methods are

  • .rowAsSingleArrayInputTensor(), which converts a single row’s inputs to the np tensor format
  • .rowAsSingleDataframeInputTensor(), which converts a single row’s inputs to the pd tensor format
    TrustyAI dataframe 2.excalidraw.svg
    If an entire dataset needs to be converted (say for batch requests), we can use:
  • .rowBatchArrayInputTensor(), converts an entire dataframe to the np tensor format
  • rowBatchDataframeInputTensor(), converts an entire dataframe to the pd tensor format
    TrustyAI dataframe 3.excalidraw.svg

Schemas

A table with the values of the schemas for each scenario.

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 1 output, no batchNP1111[0,1]1[0,1]
1 input, 2 outputs, no batchNP1211[1,1]1[1,2]
2 inputs, 1 output, no batchNP2111[1,2]1[1,1]
2 inputs, 2 outputs, no batchNP2211[1,2]1[1,2]
1 input, 1 output, no batchPD1111[1]1[1]
1 input, 2 outputs, no batchPD1211[1]2[1],[1]
2 inputs, 1 output, no batchPD2112[1],[1]1[1]
2 inputs, 2 outputs, no batchPD2212[1],[1]2[1],[1]
1 input, 1 output, batchNP11101[10, 1]1[10, 1]
1 input, 2 outputs, batchNP12101[10,1]1[10,2]
2 inputs, 1 output, batchNP21101[10,2]1[10,1]
2 inputs, 2 outputs, batchNP22101[10,2]1[10,2]
1 input, 1 output, batchPD11101[1, 10]1[1, 10]
1 input, 2 outputs, batchPD12101[1, 10]2[1, 10],[1, 10]
2 inputs, 1 output, batchPD21102[1, 10],[1, 10]1[1, 10]
2 inputs, 2 outputs, batchPD22102[1, 10],[1, 10]2[1, 10],[1, 10]

Which schemas have $>1$ I/O counts?

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 2 outputs, no batchPD1211[1]2[1],[1]
2 inputs, 1 output, no batchPD2112[1],[1]1[1]
2 inputs, 2 outputs, no batchPD2212[1],[1]2[1],[1]
1 input, 2 outputs, batchPD12101[1, 10]2[1, 10],[1, 10]
2 inputs, 1 output, batchPD21102[1, 10],[1, 10]1[1, 10]
2 inputs, 2 outputs, batchPD22102[1, 10],[1, 10]2[1, 10],[1, 10]

If the input/output counts are $>1$, then we definitely have a PD payload. This is regardless of batch size.

Which schemas have $=1$ I/O counts?

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 1 output, no batchNP1111[1]1[1]
1 input, 2 outputs, no batchNP1211[1,1]1[1,2]
2 inputs, 1 output, no batchNP2111[1,2]1[1,1]
2 inputs, 2 outputs, no batchNP2211[1,2]1[1,2]
1 input, 1 output, no batchPD1111[1]1[1]
1 input, 1 output, batchNP11101[10, 1]1[10, 1]
1 input, 2 outputs, batchNP12101[10,1]1[10,2]
2 inputs, 1 output, batchNP21101[10,2]1[10,1]
2 inputs, 2 outputs, batchNP22101[10,2]1[10,2]
1 input, 1 output, batchPD11101[1, 10]1[1, 10]

If the top input/ouput counts are $=1$, then we definitely have an NP (the PD in there just means a 1 input, 1 output PD, which is indistinguishable).

Let’s look at the inputs only. The first case is when the count is $=1$.

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 1 output, no batchNP1111[0,1]1[1]
1 input, 2 outputs, no batchNP1211[1,1]1[1,2]
2 inputs, 1 output, no batchNP2111[1,2]1[1,1]
2 inputs, 2 outputs, no batchNP2211[1,2]1[1,2]
1 input, 1 output, no batchPD1111[1]1[1]
1 input, 2 outputs, no batchPD1211[1]2[1],[1]
1 input, 1 output, batchNP11101[10, 1]1[10, 1]
1 input, 2 outputs, batchNP12101[10,1]1[10,2]
2 inputs, 1 output, batchNP21101[10,2]1[10,1]
2 inputs, 2 outputs, batchNP22101[10,2]1[10,2]
1 input, 1 output, batchPD11101[1, 10]1[1, 10]
1 input, 2 outputs, batchPD12101[1, 10]2[1, 10],[1, 10]

Now let’s look at the case were $>1$

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
2 inputs, 1 output, no batchPD2112[1],[1]1[1]
2 inputs, 2 outputs, no batchPD2212[1],[1]2[1],[1]
2 inputs, 1 output, batchPD21102[1, 10],[1, 10]1[1, 10]
2 inputs, 2 outputs, batchPD22102[1, 10],[1, 10]2[1, 10],[1, 10]

Logic:

flowchart TD
	A{Input count = 1?} -->|Yes| B{Shape 0 > 1}
	B -->|Yes| D[NP batch]
	B -->|No| C{Just one\nelement?}
	C -->|No| F[Either NP, no batch, multi-featute\nOR\nPD, batch, 1 feature]
	C -->|Yes| G[1 feature, no batch]
	A -->|No| H{Single element\nshapes?}
	H -->|Yes| I[Multi-feature\nPD\nNo batch]
	H -->|No| J[Multi-feature\nPD\nBatch]

Regarding the outputs, let’s first look at the case where the output counts are $=1$

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 1 output, no batchNP1111[1]1[1]
1 input, 2 outputs, no batchNP1211[1,1]1[1,2]
2 inputs, 1 output, no batchNP2111[1,2]1[1,1]
2 inputs, 2 outputs, no batchNP2211[1,2]1[1,2]
1 input, 1 output, no batchPD1111[1]1[1]
2 inputs, 1 output, no batchPD2112[1],[1]1[1]
1 input, 1 output, batchNP11101[10, 1]1[10, 1]
1 input, 2 outputs, batchNP12101[10,1]1[10,2]
2 inputs, 1 output, batchNP21101[10,2]1[10,1]
2 inputs, 2 outputs, batchNP22101[10,2]1[10,2]
1 input, 1 output, batchPD11101[1, 10]1[1, 10]

We can see the same logic as in the inputs. When the output counts $>1$:

ScenarioFormat# Input features# Output featuresBatch sizeTop inputs countInput(s) shapeTop output countsOutput(s) shape
1 input, 2 outputs, no batchPD1211[1]2[1],[1]
2 inputs, 2 outputs, no batchPD2212[1],[1]2[1],[1]
1 input, 2 outputs, batchPD12101[1, 10]2[1, 10],[1, 10]
2 inputs, 2 outputs, batchPD22102[1, 10],[1, 10]2[1, 10],[1, 10]
flowchart TD
	A{Output count = 1?} -->|Yes| B{Shape 0 > 1}
	B -->|Yes| D[NP batch]
	B -->|No| C{Just one\nelement?}
	C -->|No| F[Either NP, no batch, multi-featute\nOR\nPD, batch, 1 feature]
	C -->|Yes| G[1 feature, no batch]
	A -->|No| H{Single element\nshapes?}
	H -->|Yes| I[Multi-feature\nPD\nNo batch]
	H -->|No| J[Multi-feature\nPD\nBatch]