Synopsis
Inference data is represented in TrustyAI1 https://github.com/trustyai-explainability/trustyai-explainability 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.

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

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 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.

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 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:
 #### Outputs
For the single input case, the output conversion is straightforward. We
get a shaped tensor for the outputs, which allows for direct
conversion to a single PredictionOutput, such that

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 inputs, each with features.
np codec
With the np codec, we simply produce a shaped tensor,
sharing the same datatype.

pd codec

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 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:

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.

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

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

## Schemas
A table with the values of the schemas for each scenario.
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 1 output, no batch | NP | 1 | 1 | 1 | 1 | [0,1] | 1 | [0,1] |
| 1 input, 2 outputs, no batch | NP | 1 | 2 | 1 | 1 | [1,1] | 1 | [1,2] |
| 2 inputs, 1 output, no batch | NP | 2 | 1 | 1 | 1 | [1,2] | 1 | [1,1] |
| 2 inputs, 2 outputs, no batch | NP | 2 | 2 | 1 | 1 | [1,2] | 1 | [1,2] |
| 1 input, 1 output, no batch | PD | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 1 input, 2 outputs, no batch | PD | 1 | 2 | 1 | 1 | [1] | 2 | [1],[1] |
| 2 inputs, 1 output, no batch | PD | 2 | 1 | 1 | 2 | [1],[1] | 1 | [1] |
| 2 inputs, 2 outputs, no batch | PD | 2 | 2 | 1 | 2 | [1],[1] | 2 | [1],[1] |
| 1 input, 1 output, batch | NP | 1 | 1 | 10 | 1 | [10, 1] | 1 | [10, 1] |
| 1 input, 2 outputs, batch | NP | 1 | 2 | 10 | 1 | [10,1] | 1 | [10,2] |
| 2 inputs, 1 output, batch | NP | 2 | 1 | 10 | 1 | [10,2] | 1 | [10,1] |
| 2 inputs, 2 outputs, batch | NP | 2 | 2 | 10 | 1 | [10,2] | 1 | [10,2] |
| 1 input, 1 output, batch | PD | 1 | 1 | 10 | 1 | [1, 10] | 1 | [1, 10] |
| 1 input, 2 outputs, batch | PD | 1 | 2 | 10 | 1 | [1, 10] | 2 | [1, 10],[1, 10] |
| 2 inputs, 1 output, batch | PD | 2 | 1 | 10 | 2 | [1, 10],[1, 10] | 1 | [1, 10] |
| 2 inputs, 2 outputs, batch | PD | 2 | 2 | 10 | 2 | [1, 10],[1, 10] | 2 | [1, 10],[1, 10] |
Which schemas have I/O counts?
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 2 outputs, no batch | PD | 1 | 2 | 1 | 1 | [1] | 2 | [1],[1] |
| 2 inputs, 1 output, no batch | PD | 2 | 1 | 1 | 2 | [1],[1] | 1 | [1] |
| 2 inputs, 2 outputs, no batch | PD | 2 | 2 | 1 | 2 | [1],[1] | 2 | [1],[1] |
| 1 input, 2 outputs, batch | PD | 1 | 2 | 10 | 1 | [1, 10] | 2 | [1, 10],[1, 10] |
| 2 inputs, 1 output, batch | PD | 2 | 1 | 10 | 2 | [1, 10],[1, 10] | 1 | [1, 10] |
| 2 inputs, 2 outputs, batch | PD | 2 | 2 | 10 | 2 | [1, 10],[1, 10] | 2 | [1, 10],[1, 10] |
If the input/output counts are , then we definitely have a PD payload. This is regardless of batch size.
Which schemas have I/O counts?
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 1 output, no batch | NP | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 1 input, 2 outputs, no batch | NP | 1 | 2 | 1 | 1 | [1,1] | 1 | [1,2] |
| 2 inputs, 1 output, no batch | NP | 2 | 1 | 1 | 1 | [1,2] | 1 | [1,1] |
| 2 inputs, 2 outputs, no batch | NP | 2 | 2 | 1 | 1 | [1,2] | 1 | [1,2] |
| 1 input, 1 output, no batch | PD | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 1 input, 1 output, batch | NP | 1 | 1 | 10 | 1 | [10, 1] | 1 | [10, 1] |
| 1 input, 2 outputs, batch | NP | 1 | 2 | 10 | 1 | [10,1] | 1 | [10,2] |
| 2 inputs, 1 output, batch | NP | 2 | 1 | 10 | 1 | [10,2] | 1 | [10,1] |
| 2 inputs, 2 outputs, batch | NP | 2 | 2 | 10 | 1 | [10,2] | 1 | [10,2] |
| 1 input, 1 output, batch | PD | 1 | 1 | 10 | 1 | [1, 10] | 1 | [1, 10] |
If the top input/ouput counts are , 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 .
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 1 output, no batch | NP | 1 | 1 | 1 | 1 | [0,1] | 1 | [1] |
| 1 input, 2 outputs, no batch | NP | 1 | 2 | 1 | 1 | [1,1] | 1 | [1,2] |
| 2 inputs, 1 output, no batch | NP | 2 | 1 | 1 | 1 | [1,2] | 1 | [1,1] |
| 2 inputs, 2 outputs, no batch | NP | 2 | 2 | 1 | 1 | [1,2] | 1 | [1,2] |
| 1 input, 1 output, no batch | PD | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 1 input, 2 outputs, no batch | PD | 1 | 2 | 1 | 1 | [1] | 2 | [1],[1] |
| 1 input, 1 output, batch | NP | 1 | 1 | 10 | 1 | [10, 1] | 1 | [10, 1] |
| 1 input, 2 outputs, batch | NP | 1 | 2 | 10 | 1 | [10,1] | 1 | [10,2] |
| 2 inputs, 1 output, batch | NP | 2 | 1 | 10 | 1 | [10,2] | 1 | [10,1] |
| 2 inputs, 2 outputs, batch | NP | 2 | 2 | 10 | 1 | [10,2] | 1 | [10,2] |
| 1 input, 1 output, batch | PD | 1 | 1 | 10 | 1 | [1, 10] | 1 | [1, 10] |
| 1 input, 2 outputs, batch | PD | 1 | 2 | 10 | 1 | [1, 10] | 2 | [1, 10],[1, 10] |
Now let’s look at the case were
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 2 inputs, 1 output, no batch | PD | 2 | 1 | 1 | 2 | [1],[1] | 1 | [1] |
| 2 inputs, 2 outputs, no batch | PD | 2 | 2 | 1 | 2 | [1],[1] | 2 | [1],[1] |
| 2 inputs, 1 output, batch | PD | 2 | 1 | 10 | 2 | [1, 10],[1, 10] | 1 | [1, 10] |
| 2 inputs, 2 outputs, batch | PD | 2 | 2 | 10 | 2 | [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
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 1 output, no batch | NP | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 1 input, 2 outputs, no batch | NP | 1 | 2 | 1 | 1 | [1,1] | 1 | [1,2] |
| 2 inputs, 1 output, no batch | NP | 2 | 1 | 1 | 1 | [1,2] | 1 | [1,1] |
| 2 inputs, 2 outputs, no batch | NP | 2 | 2 | 1 | 1 | [1,2] | 1 | [1,2] |
| 1 input, 1 output, no batch | PD | 1 | 1 | 1 | 1 | [1] | 1 | [1] |
| 2 inputs, 1 output, no batch | PD | 2 | 1 | 1 | 2 | [1],[1] | 1 | [1] |
| 1 input, 1 output, batch | NP | 1 | 1 | 10 | 1 | [10, 1] | 1 | [10, 1] |
| 1 input, 2 outputs, batch | NP | 1 | 2 | 10 | 1 | [10,1] | 1 | [10,2] |
| 2 inputs, 1 output, batch | NP | 2 | 1 | 10 | 1 | [10,2] | 1 | [10,1] |
| 2 inputs, 2 outputs, batch | NP | 2 | 2 | 10 | 1 | [10,2] | 1 | [10,2] |
| 1 input, 1 output, batch | PD | 1 | 1 | 10 | 1 | [1, 10] | 1 | [1, 10] |
We can see the same logic as in the inputs. When the output counts :
| Scenario | Format | # Input features | # Output features | Batch size | Top inputs count | Input(s) shape | Top output counts | Output(s) shape |
|---|---|---|---|---|---|---|---|---|
| 1 input, 2 outputs, no batch | PD | 1 | 2 | 1 | 1 | [1] | 2 | [1],[1] |
| 2 inputs, 2 outputs, no batch | PD | 2 | 2 | 1 | 2 | [1],[1] | 2 | [1],[1] |
| 1 input, 2 outputs, batch | PD | 1 | 2 | 10 | 1 | [1, 10] | 2 | [1, 10],[1, 10] |
| 2 inputs, 2 outputs, batch | PD | 2 | 2 | 10 | 2 | [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]