Add new op ROI_POOLING.

Add reference CPU implementation for ROI_POOLING with NHWC/NCHW data layout
and FP32/QUANT8 input data type.

Create tests for both operations. Generate cts/vts tests. Testcases are
verified by execution on Caffe2 lib.

Bug: 113560033
Test: NeuralNetworksTest_static
Change-Id: Icd6925b64975ddff0a538705b342ac76f6f31249
Merged-In: Icd6925b64975ddff0a538705b342ac76f6f31249
(cherry picked from commit 5ed64a411b909d517eb879e0341e8fc9207190bc)
diff --git a/common/Android.bp b/common/Android.bp
index d932b7b..167fb5b 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -89,11 +89,12 @@
         "operations/Normalization.cpp",
         "operations/Pooling.cpp",
         "operations/Pow.cpp",
-	"operations/TopK_V2.cpp",
+        "operations/TopK_V2.cpp",
         "operations/QuantizedLSTM.cpp",
         "operations/Reshape.cpp",
         "operations/RNN.cpp",
         "operations/RoiAlign.cpp",
+        "operations/RoiPooling.cpp",
         "operations/SimpleMath.cpp",
         "operations/Split.cpp",
         "operations/StridedSlice.cpp",
diff --git a/common/CpuExecutor.cpp b/common/CpuExecutor.cpp
index fea3137..75d9ade 100644
--- a/common/CpuExecutor.cpp
+++ b/common/CpuExecutor.cpp
@@ -2196,6 +2196,47 @@
                 break;
             }
         } break;
+        case OperationType::ROI_POOLING: {
+            if (!allParametersPresent(5, 1)) {
+                return ANEURALNETWORKS_BAD_DATA;
+            }
+            const RunTimeOperandInfo& input = mOperands[ins[0]];
+            const RunTimeOperandInfo& roi = mOperands[ins[1]];
+            const RunTimeOperandInfo& outputShape = mOperands[ins[2]];
+            const float spatialScale = getScalarData<float>(mOperands[ins[3]]);
+            const bool data_layout = getScalarData<bool>(mOperands[ins[4]]);
+
+            RunTimeOperandInfo& out = mOperands[outs[0]];
+            Shape outShape = out.shape();
+
+            RunTimeOperandInfo input_tmp, out_tmp;
+            std::unique_ptr<uint8_t[]> input_tmp_guard, out_tmp_guard;
+            if (!convertToNhwc(input_tmp, input, input_tmp_guard, data_layout)) {
+                success = false;
+                break;
+            }
+            out_tmp.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+            out_tmp.buffer = data_layout ? nullptr : out.buffer;
+
+            if (!roiAlignPrepare(input_tmp.shape(), reinterpret_cast<const float*>(roi.buffer),
+                                 roi.shape(), reinterpret_cast<const int32_t*>(outputShape.buffer),
+                                 outputShape.shape(), spatialScale, &outShape) ||
+                !setInfoAndAllocateIfNeeded(&out_tmp, outShape)) {
+                success = false;
+                break;
+            }
+
+            success = roiPoolingGeneric(input_tmp.buffer, input_tmp.shape(), roi.buffer,
+                                        roi.shape(), spatialScale, out_tmp.buffer, outShape);
+
+            if (data_layout) {
+                out_tmp_guard.reset(out_tmp.buffer);
+            }
+            if (!success || !convertFromNhwc(out, out_tmp, data_layout)) {
+                success = false;
+                break;
+            }
+        } break;
         case OperationType::HEATMAP_MAX_KEYPOINT: {
             if (!allParametersPresent(3, 1)) {
                 return ANEURALNETWORKS_BAD_DATA;
diff --git a/common/Utils.cpp b/common/Utils.cpp
index cfeb93f..12e871a 100644
--- a/common/Utils.cpp
+++ b/common/Utils.cpp
@@ -2102,6 +2102,30 @@
                                                  inExpectedTypes, outputCount, outputIndexes,
                                                  outExpectedTypes);
         }
+        case ANEURALNETWORKS_ROI_POOLING: {
+            if (inputCount != 5 || outputCount != 1) {
+                logInvalidInOutNumber(5, 1);
+                return ANEURALNETWORKS_BAD_DATA;
+            }
+            std::vector<OperandType> inExpectedTypes;
+            std::vector<OperandType> outExpectedTypes;
+            auto inputType = operands[inputIndexes[0]].type;
+            if (inputType == OperandType::TENSOR_FLOAT32 ||
+                inputType == OperandType::TENSOR_QUANT8_ASYMM) {
+                inExpectedTypes = {inputType, OperandType::TENSOR_FLOAT32,
+                                   OperandType::TENSOR_INT32, OperandType::FLOAT32,
+                                   OperandType::BOOL};
+                outExpectedTypes = {inputType};
+            } else {
+                LOG(ERROR) << "Unsupported input tensor type for operation "
+                           << kOperationNames[opType];
+                return ANEURALNETWORKS_BAD_DATA;
+            }
+            NN_RETURN_IF_ERROR(validateHalVersion(opType, halVersion, HalVersion::V1_2));
+            return validateOperationOperandTypes(operands, inputCount, inputIndexes,
+                                                 inExpectedTypes, outputCount, outputIndexes,
+                                                 outExpectedTypes);
+        }
         case ANEURALNETWORKS_HEATMAP_MAX_KEYPOINT: {
             if (inputCount != 3 || outputCount != 1) {
                 logInvalidInOutNumber(3, 1);
diff --git a/common/include/Operations.h b/common/include/Operations.h
index dd195c2..5ef71d1 100644
--- a/common/include/Operations.h
+++ b/common/include/Operations.h
@@ -256,6 +256,10 @@
                     const Shape& roiShape, float spatialScale, int32_t samplingRatio,
                     uint8_t* outputData, const Shape& outputShape);
 
+bool roiPoolingGeneric(const uint8_t* inputData, const Shape& inputShape, const uint8_t* roiData,
+                       const Shape& roiShape, float spatialScale, uint8_t* outputData,
+                       const Shape& outputShape);
+
 bool heatmapMaxKeypoint(const float* heatmap, const Shape& heatmapShape, const float* boxes,
                         const Shape& boxesShape, float* outputData, const Shape& outputShape);
 
diff --git a/common/operations/RoiPooling.cpp b/common/operations/RoiPooling.cpp
new file mode 100644
index 0000000..591617f
--- /dev/null
+++ b/common/operations/RoiPooling.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "CpuOperationUtils.h"
+#include "Operations.h"
+
+#include <cfloat>
+#include <cmath>
+
+#include "Tracing.h"
+
+namespace android {
+namespace nn {
+
+template <typename T_Input>
+bool roiPoolingImpl(const T_Input* inputData, const Shape& inputShape, const float* roiData,
+                    const Shape& roiShape, float spatialScale, T_Input* outputData,
+                    const Shape& outputShape) {
+    NNTRACE_TRANS("RoiPooling");
+
+    const uint32_t kRoiDim = 4;
+
+    uint32_t inHeight = getSizeOfDimension(inputShape, 1);
+    uint32_t inWidth = getSizeOfDimension(inputShape, 2);
+    uint32_t inDepth = getSizeOfDimension(inputShape, 3);
+    uint32_t outHeight = getSizeOfDimension(outputShape, 1);
+    uint32_t outWidth = getSizeOfDimension(outputShape, 2);
+    uint32_t numRois = getSizeOfDimension(roiShape, 0);
+    uint32_t roiInfoLength = getSizeOfDimension(roiShape, 1);
+
+    T_Input* outPtr = outputData;
+    const float* roiDataEnd = roiData + numRois * roiInfoLength;
+    for (const float* roiInfo = roiData; roiInfo < roiDataEnd; roiInfo += kRoiDim) {
+        uint32_t batchId = 0;
+        // get optional batch id
+        if (roiInfoLength == kRoiDim + 1) {
+            batchId = std::round(roiInfo[0]);
+            roiInfo++;
+        }
+        const T_Input* batchBase = inputData + batchId * inHeight * inWidth * inDepth;
+
+        int32_t wRoiStart = std::round(roiInfo[0] * spatialScale);
+        int32_t hRoiStart = std::round(roiInfo[1] * spatialScale);
+        int32_t wRoiEnd = std::round(roiInfo[2] * spatialScale);
+        int32_t hRoiEnd = std::round(roiInfo[3] * spatialScale);
+
+        // Rois with width/height < 1 are considered malformed and are forced to be 1
+        float roiWidth = static_cast<float>(std::max(wRoiEnd - wRoiStart + 1, 1));
+        float roiHeight = static_cast<float>(std::max(hRoiEnd - hRoiStart + 1, 1));
+        float wStepSize = roiWidth / static_cast<float>(outWidth);
+        float hStepSize = roiHeight / static_cast<float>(outHeight);
+
+        for (uint32_t i = 0; i < outHeight; i++) {
+            for (uint32_t j = 0; j < outWidth; j++) {
+                // Take floor on start, ceil on end, start included, end excluded, i.e. [start, end)
+                // end is guaranteed to larger than start by at least 1
+                uint32_t wStart = std::floor(wStepSize * j + wRoiStart);
+                uint32_t wEnd = std::ceil(wStepSize * (j + 1) + wRoiStart);
+                uint32_t hStart = std::floor(hStepSize * i + hRoiStart);
+                uint32_t hEnd = std::ceil(hStepSize * (i + 1) + hRoiStart);
+
+                wStart = std::min(wStart, inWidth);
+                wEnd = std::min(wEnd, inWidth);
+                hStart = std::min(hStart, inHeight);
+                hEnd = std::min(hEnd, inHeight);
+
+                for (uint32_t k = 0; k < inDepth; k++) {
+                    T_Input maxValue;
+                    bool first = true;
+                    for (uint32_t h = hStart; h < hEnd; h++) {
+                        for (uint32_t w = wStart; w < wEnd; w++) {
+                            T_Input inputValue = batchBase[h * inWidth * inDepth + w * inDepth + k];
+                            if (first || inputValue > maxValue) {
+                                maxValue = inputValue;
+                                first = false;
+                            }
+                        }
+                    }
+                    outPtr[k] = maxValue;
+                }
+                outPtr += inDepth;
+            }
+        }
+    }
+    return true;
+}
+
+bool roiPoolingGeneric(const uint8_t* inputData, const Shape& inputShape, const uint8_t* roiData,
+                       const Shape& roiShape, float spatialScale, uint8_t* outputData,
+                       const Shape& outputShape) {
+    NNTRACE_TRANS("roiPoolingGeneric");
+    if (inputShape.type == OperandType::TENSOR_FLOAT32) {
+        return roiPoolingImpl<float>(reinterpret_cast<const float*>(inputData), inputShape,
+                                     reinterpret_cast<const float*>(roiData), roiShape,
+                                     spatialScale, reinterpret_cast<float*>(outputData),
+                                     outputShape);
+    } else if (inputShape.type == OperandType::TENSOR_QUANT8_ASYMM) {
+        return roiPoolingImpl<uint8_t>(reinterpret_cast<const uint8_t*>(inputData), inputShape,
+                                       reinterpret_cast<const float*>(roiData), roiShape,
+                                       spatialScale, reinterpret_cast<uint8_t*>(outputData),
+                                       outputShape);
+    } else {
+        LOG(ERROR) << "Unsupported data type";
+        return false;
+    }
+}
+
+}  // namespace nn
+}  // namespace android
diff --git a/runtime/include/NeuralNetworks.h b/runtime/include/NeuralNetworks.h
index 9735bba..6b73bf7 100644
--- a/runtime/include/NeuralNetworks.h
+++ b/runtime/include/NeuralNetworks.h
@@ -2947,6 +2947,42 @@
      */
     ANEURALNETWORKS_ABS = 88,
 
+    /**
+     * Select and scale the feature map of each region of interest to a unified
+     * output size by max-pooling.
+     *
+     * The region of interest is represented by its upper-left corner coordinate
+     * (x1,y1) and lower-right corner coordinate (x2,y2) in the original image.
+     * A spatial scaling factor is applied to map into feature map coordinate.
+     * A valid region of interest should satisfy x1 < x2 and y1 < y2.
+     *
+     * Rounding is applied in this operation to ensure integer boundary for
+     * regions of interest and pooling bins.
+     *
+     * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout.
+     * With the default data layout NHWC, the data is stored in the order of:
+     * [batch, height, width, channels]. Alternatively, the data layout could
+     * be NCHW, the data storage order of: [batch, channels, height, width].
+     *
+     * Inputs:
+     * * 0: A 4-D tensor, specifying the feature map.
+     * * 1: A 2-D Tensor of shape [num_rois, 5 or 4], specifying the locations
+     *      of the regions of interest, each line with format
+     *      [<optional batch_id>, x1, y1, x2, y2]. The batch_id is optional if
+     *      there is only one batch.
+     * * 2: A 1-D Tensor of {@link ANEURALNETWORKS_TENSOR_INT32},
+     *      specifying the size of the output tensor [out_height, out_width].
+     * * 3: An {@link ANEURALNETWORKS_FLOAT32} scalar, specifying the spatial
+     *      scaling factor from original image to feature map.
+     * * 4: An {@link ANEURALNETWORKS_BOOL} scalar, set to true to specify
+     *      NCHW data layout for input0 and output0. Set to false for NHWC.
+     *
+     * Outputs:
+     * * 0: A tensor of the same {@link OperandCode} as input0. The output
+     *      shape is [num_rois, out_height, out_width, depth].
+     *
+     * Available since API level 29.
+     */
     ANEURALNETWORKS_ROI_POOLING = 89,
 } OperationCode;
 
diff --git a/runtime/test/for-cts/TestGeneratedOneFile.cpp b/runtime/test/for-cts/TestGeneratedOneFile.cpp
index 704a74f..6243407 100644
--- a/runtime/test/for-cts/TestGeneratedOneFile.cpp
+++ b/runtime/test/for-cts/TestGeneratedOneFile.cpp
@@ -397,6 +397,7 @@
 #include "../generated/tests/reshape_float16.mod.py.cpp"
 #include "../generated/tests/resize_bilinear_v1_2.mod.py.cpp"
 #include "../generated/tests/roi_align.mod.py.cpp"
+#include "../generated/tests/roi_pooling.mod.py.cpp"
 #include "../generated/tests/rotated_bbox_transform.mod.py.cpp"
 #include "../generated/tests/softmax_v1_2.mod.py.cpp"
 #include "../generated/tests/space_to_batch_v1_2.mod.py.cpp"
diff --git a/runtime/test/generated/examples/roi_pooling.example.cpp b/runtime/test/generated/examples/roi_pooling.example.cpp
new file mode 100644
index 0000000..3e30d1e
--- /dev/null
+++ b/runtime/test/generated/examples/roi_pooling.example.cpp
@@ -0,0 +1,410 @@
+// clang-format off
+// Generated file (from: roi_pooling.mod.py). Do not edit
+std::vector<MixedTypedExample> examples_nhwc = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-10.0f, -1.0f, 4.0f, -5.0f, -8.0f, -2.0f, 9.0f, 1.0f, 7.0f, -2.0f, 3.0f, -7.0f, -2.0f, 10.0f, -3.0f, 5.0f}}, {1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-2.0f, 9.0f, -2.0f, 3.0f, -1.0f, 9.0f, 10.0f, 5.0f, -1.0f, 9.0f, 10.0f, 3.0f, -2.0f, 9.0f, 7.0f, 3.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nhwc_relaxed = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-10.0f, -1.0f, 4.0f, -5.0f, -8.0f, -2.0f, 9.0f, 1.0f, 7.0f, -2.0f, 3.0f, -7.0f, -2.0f, 10.0f, -3.0f, 5.0f}}, {1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-2.0f, 9.0f, -2.0f, 3.0f, -1.0f, 9.0f, 10.0f, 5.0f, -1.0f, 9.0f, 10.0f, 3.0f, -2.0f, 9.0f, 7.0f, 3.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nhwc_quant8 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {88, 124, 144, 108, 96, 120, 164, 132, 156, 120, 140, 100, 120, 168, 116, 148}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {120, 164, 120, 140, 124, 164, 168, 148, 124, 164, 168, 140, 120, 164, 156, 140}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-10.0f, -1.0f, 4.0f, -5.0f, -8.0f, -2.0f, 9.0f, 1.0f, 7.0f, -2.0f, 3.0f, -7.0f, -2.0f, 10.0f, -3.0f, 5.0f}}, {1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-2.0f, 9.0f, -2.0f, 3.0f, -1.0f, 9.0f, 10.0f, 5.0f, -1.0f, 9.0f, 10.0f, 3.0f, -2.0f, 9.0f, 7.0f, 3.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw_relaxed = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-10.0f, -1.0f, 4.0f, -5.0f, -8.0f, -2.0f, 9.0f, 1.0f, 7.0f, -2.0f, 3.0f, -7.0f, -2.0f, 10.0f, -3.0f, 5.0f}}, {1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {-2.0f, 9.0f, -2.0f, 3.0f, -1.0f, 9.0f, 10.0f, 5.0f, -1.0f, 9.0f, 10.0f, 3.0f, -2.0f, 9.0f, 7.0f, 3.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw_quant8 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{1, {2.0f, 2.0f, 4.0f, 4.0f, 0.0f, 0.0f, 6.0f, 6.0f, 2.0f, 0.0f, 4.0f, 6.0f, 0.0f, 2.0f, 6.0f, 4.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {88, 124, 144, 108, 96, 120, 164, 132, 156, 120, 140, 100, 120, 168, 116, 148}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {120, 164, 120, 140, 124, 164, 168, 148, 124, 164, 168, 140, 120, 164, 156, 140}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nhwc_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {8.84f, 8.88f, 7.41f, 5.6f, 9.95f, 4.37f, 0.1f, 7.64f, 6.5f, 9.47f, 7.55f, 3.0f, 0.89f, 3.01f, 6.3f, 4.4f, 1.64f, 6.74f, 6.16f, 8.6f, 5.85f, 3.17f, 7.12f, 6.79f, 5.77f, 6.62f, 5.13f, 8.44f, 5.08f, 7.12f, 2.84f, 1.19f, 8.37f, 0.9f, 7.86f, 9.69f, 1.97f, 1.31f, 4.42f, 9.89f, 0.18f, 9.0f, 9.3f, 0.44f, 5.05f, 6.47f, 1.09f, 9.5f, 1.3f, 2.18f, 2.05f, 7.74f, 7.66f, 0.65f, 4.18f, 7.14f, 5.35f, 7.9f, 1.04f, 1.47f, 9.01f, 0.95f, 4.07f, 0.65f, 5.47f, 2.64f, 0.86f, 4.86f, 2.38f, 2.45f, 8.77f, 0.06f, 3.6f, 9.28f, 5.84f, 8.97f, 6.89f, 1.43f, 3.9f, 5.91f, 7.4f, 9.25f, 3.12f, 4.92f, 1.87f, 3.22f, 9.5f, 6.73f, 2.07f, 7.3f, 3.07f, 4.97f, 0.24f, 8.91f, 1.09f, 0.27f, 7.29f, 6.94f, 2.31f, 6.88f, 4.33f, 1.37f, 0.86f, 0.46f, 6.07f, 3.81f, 0.86f, 6.99f, 4.36f, 1.92f, 8.19f, 3.57f, 7.9f, 6.78f, 4.64f, 6.82f, 6.18f, 9.63f, 2.63f, 2.33f, 1.36f, 2.7f, 9.99f, 9.85f, 8.06f, 4.8f, 7.8f, 5.43f}}, {1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {6.16f, 8.6f, 7.12f, 6.79f, 5.13f, 8.44f, 7.86f, 9.69f, 4.42f, 9.89f, 9.3f, 6.47f, 7.86f, 9.89f, 9.3f, 9.89f, 9.3f, 9.5f, 7.86f, 9.89f, 9.3f, 9.89f, 9.3f, 9.5f, 9.5f, 6.73f, 9.5f, 9.28f, 6.89f, 8.97f, 6.18f, 9.63f, 9.99f, 9.85f, 9.99f, 9.85f, 7.29f, 6.94f, 7.29f, 6.94f, 2.31f, 6.88f, 7.9f, 6.78f, 7.9f, 6.82f, 4.64f, 6.82f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nhwc_relaxed_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {8.84f, 8.88f, 7.41f, 5.6f, 9.95f, 4.37f, 0.1f, 7.64f, 6.5f, 9.47f, 7.55f, 3.0f, 0.89f, 3.01f, 6.3f, 4.4f, 1.64f, 6.74f, 6.16f, 8.6f, 5.85f, 3.17f, 7.12f, 6.79f, 5.77f, 6.62f, 5.13f, 8.44f, 5.08f, 7.12f, 2.84f, 1.19f, 8.37f, 0.9f, 7.86f, 9.69f, 1.97f, 1.31f, 4.42f, 9.89f, 0.18f, 9.0f, 9.3f, 0.44f, 5.05f, 6.47f, 1.09f, 9.5f, 1.3f, 2.18f, 2.05f, 7.74f, 7.66f, 0.65f, 4.18f, 7.14f, 5.35f, 7.9f, 1.04f, 1.47f, 9.01f, 0.95f, 4.07f, 0.65f, 5.47f, 2.64f, 0.86f, 4.86f, 2.38f, 2.45f, 8.77f, 0.06f, 3.6f, 9.28f, 5.84f, 8.97f, 6.89f, 1.43f, 3.9f, 5.91f, 7.4f, 9.25f, 3.12f, 4.92f, 1.87f, 3.22f, 9.5f, 6.73f, 2.07f, 7.3f, 3.07f, 4.97f, 0.24f, 8.91f, 1.09f, 0.27f, 7.29f, 6.94f, 2.31f, 6.88f, 4.33f, 1.37f, 0.86f, 0.46f, 6.07f, 3.81f, 0.86f, 6.99f, 4.36f, 1.92f, 8.19f, 3.57f, 7.9f, 6.78f, 4.64f, 6.82f, 6.18f, 9.63f, 2.63f, 2.33f, 1.36f, 2.7f, 9.99f, 9.85f, 8.06f, 4.8f, 7.8f, 5.43f}}, {1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {6.16f, 8.6f, 7.12f, 6.79f, 5.13f, 8.44f, 7.86f, 9.69f, 4.42f, 9.89f, 9.3f, 6.47f, 7.86f, 9.89f, 9.3f, 9.89f, 9.3f, 9.5f, 7.86f, 9.89f, 9.3f, 9.89f, 9.3f, 9.5f, 9.5f, 6.73f, 9.5f, 9.28f, 6.89f, 8.97f, 6.18f, 9.63f, 9.99f, 9.85f, 9.99f, 9.85f, 7.29f, 6.94f, 7.29f, 6.94f, 2.31f, 6.88f, 7.9f, 6.78f, 7.9f, 6.82f, 4.64f, 6.82f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nhwc_quant8_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {221, 222, 185, 140, 249, 109, 2, 191, 162, 237, 189, 75, 22, 75, 158, 110, 41, 168, 154, 215, 146, 79, 178, 170, 144, 166, 128, 211, 127, 178, 71, 30, 209, 22, 197, 242, 49, 33, 111, 247, 5, 225, 233, 11, 126, 162, 27, 238, 32, 55, 51, 194, 192, 16, 104, 178, 134, 198, 26, 37, 225, 24, 102, 16, 137, 66, 22, 122, 60, 61, 219, 2, 90, 232, 146, 224, 172, 36, 98, 148, 185, 231, 78, 123, 47, 80, 238, 168, 52, 183, 77, 124, 6, 223, 27, 7, 182, 174, 58, 172, 108, 34, 22, 12, 152, 95, 22, 175, 109, 48, 205, 89, 198, 170, 116, 171, 154, 241, 66, 58, 34, 68, 250, 246, 202, 120, 195, 136}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {154, 215, 178, 170, 128, 211, 197, 242, 111, 247, 233, 162, 197, 247, 233, 247, 233, 238, 197, 247, 233, 247, 233, 238, 238, 168, 238, 232, 172, 224, 154, 241, 250, 246, 250, 246, 182, 174, 182, 174, 58, 172, 198, 170, 198, 171, 116, 171}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {8.84f, 7.41f, 9.95f, 0.1f, 6.5f, 7.55f, 0.89f, 6.3f, 1.64f, 6.16f, 5.85f, 7.12f, 5.77f, 5.13f, 5.08f, 2.84f, 8.37f, 7.86f, 1.97f, 4.42f, 0.18f, 9.3f, 5.05f, 1.09f, 1.3f, 2.05f, 7.66f, 4.18f, 5.35f, 1.04f, 9.01f, 4.07f, 8.88f, 5.6f, 4.37f, 7.64f, 9.47f, 3.0f, 3.01f, 4.4f, 6.74f, 8.6f, 3.17f, 6.79f, 6.62f, 8.44f, 7.12f, 1.19f, 0.9f, 9.69f, 1.31f, 9.89f, 9.0f, 0.44f, 6.47f, 9.5f, 2.18f, 7.74f, 0.65f, 7.14f, 7.9f, 1.47f, 0.95f, 0.65f, 5.47f, 0.86f, 2.38f, 8.77f, 3.6f, 5.84f, 6.89f, 3.9f, 7.4f, 3.12f, 1.87f, 9.5f, 2.07f, 3.07f, 0.24f, 1.09f, 7.29f, 2.31f, 4.33f, 0.86f, 6.07f, 0.86f, 4.36f, 8.19f, 7.9f, 4.64f, 6.18f, 2.63f, 1.36f, 9.99f, 8.06f, 7.8f, 2.64f, 4.86f, 2.45f, 0.06f, 9.28f, 8.97f, 1.43f, 5.91f, 9.25f, 4.92f, 3.22f, 6.73f, 7.3f, 4.97f, 8.91f, 0.27f, 6.94f, 6.88f, 1.37f, 0.46f, 3.81f, 6.99f, 1.92f, 3.57f, 6.78f, 6.82f, 9.63f, 2.33f, 2.7f, 9.85f, 4.8f, 5.43f}}, {1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {6.16f, 7.12f, 5.13f, 7.86f, 4.42f, 9.3f, 8.6f, 6.79f, 8.44f, 9.69f, 9.89f, 6.47f, 7.86f, 9.3f, 9.3f, 7.86f, 9.3f, 9.3f, 9.89f, 9.89f, 9.5f, 9.89f, 9.89f, 9.5f, 9.5f, 9.5f, 6.89f, 6.18f, 9.99f, 9.99f, 6.73f, 9.28f, 8.97f, 9.63f, 9.85f, 9.85f, 7.29f, 7.29f, 2.31f, 7.9f, 7.9f, 4.64f, 6.94f, 6.94f, 6.88f, 6.78f, 6.82f, 6.82f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw_relaxed_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {8.84f, 7.41f, 9.95f, 0.1f, 6.5f, 7.55f, 0.89f, 6.3f, 1.64f, 6.16f, 5.85f, 7.12f, 5.77f, 5.13f, 5.08f, 2.84f, 8.37f, 7.86f, 1.97f, 4.42f, 0.18f, 9.3f, 5.05f, 1.09f, 1.3f, 2.05f, 7.66f, 4.18f, 5.35f, 1.04f, 9.01f, 4.07f, 8.88f, 5.6f, 4.37f, 7.64f, 9.47f, 3.0f, 3.01f, 4.4f, 6.74f, 8.6f, 3.17f, 6.79f, 6.62f, 8.44f, 7.12f, 1.19f, 0.9f, 9.69f, 1.31f, 9.89f, 9.0f, 0.44f, 6.47f, 9.5f, 2.18f, 7.74f, 0.65f, 7.14f, 7.9f, 1.47f, 0.95f, 0.65f, 5.47f, 0.86f, 2.38f, 8.77f, 3.6f, 5.84f, 6.89f, 3.9f, 7.4f, 3.12f, 1.87f, 9.5f, 2.07f, 3.07f, 0.24f, 1.09f, 7.29f, 2.31f, 4.33f, 0.86f, 6.07f, 0.86f, 4.36f, 8.19f, 7.9f, 4.64f, 6.18f, 2.63f, 1.36f, 9.99f, 8.06f, 7.8f, 2.64f, 4.86f, 2.45f, 0.06f, 9.28f, 8.97f, 1.43f, 5.91f, 9.25f, 4.92f, 3.22f, 6.73f, 7.3f, 4.97f, 8.91f, 0.27f, 6.94f, 6.88f, 1.37f, 0.46f, 3.81f, 6.99f, 1.92f, 3.57f, 6.78f, 6.82f, 9.63f, 2.33f, 2.7f, 9.85f, 4.8f, 5.43f}}, {1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{0, {6.16f, 7.12f, 5.13f, 7.86f, 4.42f, 9.3f, 8.6f, 6.79f, 8.44f, 9.69f, 9.89f, 6.47f, 7.86f, 9.3f, 9.3f, 7.86f, 9.3f, 9.3f, 9.89f, 9.89f, 9.5f, 9.89f, 9.89f, 9.5f, 9.5f, 9.5f, 6.89f, 6.18f, 9.99f, 9.99f, 6.73f, 9.28f, 8.97f, 9.63f, 9.85f, 9.85f, 7.29f, 7.29f, 2.31f, 7.9f, 7.9f, 4.64f, 6.94f, 6.94f, 6.88f, 6.78f, 6.82f, 6.82f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
+std::vector<MixedTypedExample> examples_nchw_quant8_2 = {
+// Begin of an example
+{
+.operands = {
+//Input(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {{1, {0.0f, 4.0f, 4.0f, 24.0f, 8.0f, 0.0f, 4.0f, 4.0f, 28.0f, 12.0f, 1.0f, 7.0f, 1.0f, 25.0f, 11.0f, 1.0f, 1.0f, 7.0f, 5.0f, 11.0f}}},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {221, 185, 249, 2, 162, 189, 22, 158, 41, 154, 146, 178, 144, 128, 127, 71, 209, 197, 49, 111, 5, 233, 126, 27, 32, 51, 192, 104, 134, 26, 225, 102, 222, 140, 109, 191, 237, 75, 75, 110, 168, 215, 79, 170, 166, 211, 178, 30, 22, 242, 33, 247, 225, 11, 162, 238, 55, 194, 16, 178, 198, 37, 24, 16, 137, 22, 60, 219, 90, 146, 172, 98, 185, 78, 47, 238, 52, 77, 6, 27, 182, 58, 108, 22, 152, 22, 109, 205, 198, 116, 154, 66, 34, 250, 202, 195, 66, 122, 61, 2, 232, 224, 36, 148, 231, 123, 80, 168, 183, 124, 223, 7, 174, 172, 34, 12, 95, 175, 48, 89, 170, 171, 241, 58, 68, 246, 120, 136}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+},
+//Output(s)
+{ // See tools/test_generator/include/TestHarness.h:MixedTyped
+  // int -> FLOAT32 map
+  {},
+  // int -> INT32 map
+  {},
+  // int -> QUANT8_ASYMM map
+  {{0, {154, 178, 128, 197, 111, 233, 215, 170, 211, 242, 247, 162, 197, 233, 233, 197, 233, 233, 247, 247, 238, 247, 247, 238, 238, 238, 172, 154, 250, 250, 168, 232, 224, 241, 246, 246, 182, 182, 58, 198, 198, 116, 174, 174, 172, 170, 171, 171}}},
+  // int -> QUANT16_SYMM map
+  {},
+  // int -> FLOAT16 map
+  {},
+}
+},
+}, // End of an example
+};
+
diff --git a/runtime/test/generated/models/roi_pooling.model.cpp b/runtime/test/generated/models/roi_pooling.model.cpp
new file mode 100644
index 0000000..6d77315
--- /dev/null
+++ b/runtime/test/generated/models/roi_pooling.model.cpp
@@ -0,0 +1,418 @@
+// clang-format off
+// Generated file (from: roi_pooling.mod.py). Do not edit
+void CreateModel_nhwc(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type1(Type::TENSOR_FLOAT32, {1, 4, 4, 1});
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type3(Type::TENSOR_FLOAT32, {4, 2, 2, 1});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  // Phase 1, operands
+  auto in = model->addOperand(&type1);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type3);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nhwc_relaxed(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type1(Type::TENSOR_FLOAT32, {1, 4, 4, 1});
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type3(Type::TENSOR_FLOAT32, {4, 2, 2, 1});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  // Phase 1, operands
+  auto in = model->addOperand(&type1);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type3);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  // Phase 4: set relaxed execution
+  model->relaxComputationFloat32toFloat16(true);
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc_relaxed(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nhwc_quant8(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type10(Type::TENSOR_QUANT8_ASYMM, {4, 2, 2, 1}, 0.25f, 128);
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type9(Type::TENSOR_QUANT8_ASYMM, {1, 4, 4, 1}, 0.25f, 128);
+  // Phase 1, operands
+  auto in = model->addOperand(&type9);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type10);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc_quant8(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type11(Type::TENSOR_FLOAT32, {1, 1, 4, 4});
+  OperandType type12(Type::TENSOR_FLOAT32, {4, 1, 2, 2});
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  // Phase 1, operands
+  auto in = model->addOperand(&type11);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type12);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw_relaxed(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type11(Type::TENSOR_FLOAT32, {1, 1, 4, 4});
+  OperandType type12(Type::TENSOR_FLOAT32, {4, 1, 2, 2});
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  // Phase 1, operands
+  auto in = model->addOperand(&type11);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type12);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  // Phase 4: set relaxed execution
+  model->relaxComputationFloat32toFloat16(true);
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw_relaxed(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw_quant8(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type13(Type::TENSOR_QUANT8_ASYMM, {1, 1, 4, 4}, 0.25f, 128);
+  OperandType type14(Type::TENSOR_QUANT8_ASYMM, {4, 1, 2, 2}, 0.25f, 128);
+  OperandType type2(Type::TENSOR_FLOAT32, {4, 4});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  // Phase 1, operands
+  auto in = model->addOperand(&type13);
+  auto roi = model->addOperand(&type2);
+  auto param = model->addOperand(&type4);
+  auto param1 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out = model->addOperand(&type14);
+  // Phase 2, operations
+  static int32_t param_init[] = {2, 2};
+  model->setOperandValue(param, param_init, sizeof(int32_t) * 2);
+  static float param1_init[] = {0.5f};
+  model->setOperandValue(param1, param1_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in, roi, param, param1, layout}, {out});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in, roi},
+    {out});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw_quant8(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nhwc_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type6(Type::TENSOR_FLOAT32, {2, 4, 8, 2});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  OperandType type8(Type::TENSOR_FLOAT32, {4, 2, 3, 2});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type6);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type8);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nhwc_relaxed_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type6(Type::TENSOR_FLOAT32, {2, 4, 8, 2});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  OperandType type8(Type::TENSOR_FLOAT32, {4, 2, 3, 2});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type6);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type8);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  // Phase 4: set relaxed execution
+  model->relaxComputationFloat32toFloat16(true);
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc_relaxed_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nhwc_quant8_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type15(Type::TENSOR_QUANT8_ASYMM, {2, 4, 8, 2}, 0.04f, 0);
+  OperandType type16(Type::TENSOR_QUANT8_ASYMM, {4, 2, 3, 2}, 0.04f, 0);
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type15);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type16);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {false};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nhwc_quant8_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type17(Type::TENSOR_FLOAT32, {2, 2, 4, 8});
+  OperandType type18(Type::TENSOR_FLOAT32, {4, 2, 2, 3});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type17);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type18);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw_relaxed_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type17(Type::TENSOR_FLOAT32, {2, 2, 4, 8});
+  OperandType type18(Type::TENSOR_FLOAT32, {4, 2, 2, 3});
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type17);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type18);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  // Phase 4: set relaxed execution
+  model->relaxComputationFloat32toFloat16(true);
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw_relaxed_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
+void CreateModel_nchw_quant8_2(Model *model) {
+  OperandType type0(Type::BOOL, {});
+  OperandType type19(Type::TENSOR_QUANT8_ASYMM, {2, 2, 4, 8}, 0.04f, 0);
+  OperandType type20(Type::TENSOR_QUANT8_ASYMM, {4, 2, 2, 3}, 0.04f, 0);
+  OperandType type4(Type::TENSOR_INT32, {2});
+  OperandType type5(Type::FLOAT32, {});
+  OperandType type7(Type::TENSOR_FLOAT32, {4, 5});
+  // Phase 1, operands
+  auto in1 = model->addOperand(&type19);
+  auto roi1 = model->addOperand(&type7);
+  auto param2 = model->addOperand(&type4);
+  auto param3 = model->addOperand(&type5);
+  auto layout = model->addOperand(&type0);
+  auto out1 = model->addOperand(&type20);
+  // Phase 2, operations
+  static int32_t param2_init[] = {2, 3};
+  model->setOperandValue(param2, param2_init, sizeof(int32_t) * 2);
+  static float param3_init[] = {0.25f};
+  model->setOperandValue(param3, param3_init, sizeof(float) * 1);
+  static bool layout_init[] = {true};
+  model->setOperandValue(layout, layout_init, sizeof(bool) * 1);
+  model->addOperation(ANEURALNETWORKS_ROI_POOLING, {in1, roi1, param2, param3, layout}, {out1});
+  // Phase 3, inputs and outputs
+  model->identifyInputsAndOutputs(
+    {in1, roi1},
+    {out1});
+  assert(model->isValid());
+}
+
+inline bool is_ignored_nchw_quant8_2(int i) {
+  static std::set<int> ignore = {};
+  return ignore.find(i) != ignore.end();
+}
+
diff --git a/runtime/test/generated/tests/roi_pooling.mod.py.cpp b/runtime/test/generated/tests/roi_pooling.mod.py.cpp
new file mode 100644
index 0000000..98ebb53
--- /dev/null
+++ b/runtime/test/generated/tests/roi_pooling.mod.py.cpp
@@ -0,0 +1,83 @@
+// clang-format off
+// Generated file (from: roi_pooling.mod.py). Do not edit
+#include "../../TestGenerated.h"
+
+namespace roi_pooling {
+// Generated roi_pooling test
+#include "generated/examples/roi_pooling.example.cpp"
+// Generated model constructor
+#include "generated/models/roi_pooling.model.cpp"
+} // namespace roi_pooling
+
+TEST_F(GeneratedTests, roi_pooling_nhwc) {
+    execute(roi_pooling::CreateModel_nhwc,
+            roi_pooling::is_ignored_nhwc,
+            roi_pooling::examples_nhwc);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nhwc_relaxed) {
+    execute(roi_pooling::CreateModel_nhwc_relaxed,
+            roi_pooling::is_ignored_nhwc_relaxed,
+            roi_pooling::examples_nhwc_relaxed);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nhwc_quant8) {
+    execute(roi_pooling::CreateModel_nhwc_quant8,
+            roi_pooling::is_ignored_nhwc_quant8,
+            roi_pooling::examples_nhwc_quant8);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw) {
+    execute(roi_pooling::CreateModel_nchw,
+            roi_pooling::is_ignored_nchw,
+            roi_pooling::examples_nchw);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw_relaxed) {
+    execute(roi_pooling::CreateModel_nchw_relaxed,
+            roi_pooling::is_ignored_nchw_relaxed,
+            roi_pooling::examples_nchw_relaxed);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw_quant8) {
+    execute(roi_pooling::CreateModel_nchw_quant8,
+            roi_pooling::is_ignored_nchw_quant8,
+            roi_pooling::examples_nchw_quant8);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nhwc_2) {
+    execute(roi_pooling::CreateModel_nhwc_2,
+            roi_pooling::is_ignored_nhwc_2,
+            roi_pooling::examples_nhwc_2);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nhwc_relaxed_2) {
+    execute(roi_pooling::CreateModel_nhwc_relaxed_2,
+            roi_pooling::is_ignored_nhwc_relaxed_2,
+            roi_pooling::examples_nhwc_relaxed_2);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nhwc_quant8_2) {
+    execute(roi_pooling::CreateModel_nhwc_quant8_2,
+            roi_pooling::is_ignored_nhwc_quant8_2,
+            roi_pooling::examples_nhwc_quant8_2);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw_2) {
+    execute(roi_pooling::CreateModel_nchw_2,
+            roi_pooling::is_ignored_nchw_2,
+            roi_pooling::examples_nchw_2);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw_relaxed_2) {
+    execute(roi_pooling::CreateModel_nchw_relaxed_2,
+            roi_pooling::is_ignored_nchw_relaxed_2,
+            roi_pooling::examples_nchw_relaxed_2);
+}
+
+TEST_F(GeneratedTests, roi_pooling_nchw_quant8_2) {
+    execute(roi_pooling::CreateModel_nchw_quant8_2,
+            roi_pooling::is_ignored_nchw_quant8_2,
+            roi_pooling::examples_nchw_quant8_2);
+}
+
diff --git a/runtime/test/specs/V1_2/roi_pooling.mod.py b/runtime/test/specs/V1_2/roi_pooling.mod.py
new file mode 100644
index 0000000..243bb76
--- /dev/null
+++ b/runtime/test/specs/V1_2/roi_pooling.mod.py
@@ -0,0 +1,94 @@
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# 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.
+#
+
+layout = BoolScalar("layout", False) # NHWC
+
+# TEST 1: ROI_ALIGN_1, outputShape = [2, 2], spatialScale = 0.5, samplingRatio = 4
+i1 = Input("in", "TENSOR_FLOAT32", "{1, 4, 4, 1}")
+roi1 = Input("roi", "TENSOR_FLOAT32", "{4, 4}")
+o1 = Output("out", "TENSOR_FLOAT32", "{4, 2, 2, 1}")
+Model().Operation("ROI_POOLING", i1, roi1, [2, 2], 0.5, layout).To(o1)
+
+quant8 = DataTypeConverter().Identify({
+    i1: ("TENSOR_QUANT8_ASYMM", 0.25, 128),
+    o1: ("TENSOR_QUANT8_ASYMM", 0.25, 128)
+})
+
+# Instantiate an example
+Example({
+    i1: [
+        -10, -1,  4, -5,
+        -8, -2,  9,  1,
+         7, -2,  3, -7,
+        -2, 10, -3,  5
+    ],
+    roi1: [
+        2, 2, 4, 4,
+        0, 0, 6, 6,
+        2, 0, 4, 6,
+        0, 2, 6, 4
+    ],
+    o1: [
+        -2, 9, -2, 3,
+        -1, 9, 10, 5,
+        -1, 9, 10, 3,
+        -2, 9,  7, 3
+    ]
+}).AddNchw(i1, o1, layout).AddVariations("relaxed", quant8)
+
+
+# TEST 2: ROI_ALIGN_2, outputShape = [2, 3], spatialScale = 0.25, samplingRatio = 4
+i2 = Input("in", "TENSOR_FLOAT32", "{2, 4, 8, 2}")
+roi2 = Input("roi", "TENSOR_FLOAT32", "{4, 5}")
+o2 = Output("out", "TENSOR_FLOAT32", "{4, 2, 3, 2}")
+Model().Operation("ROI_POOLING", i2, roi2, [2, 3], 0.25, layout).To(o2)
+
+quant8 = DataTypeConverter().Identify({
+    i2: ("TENSOR_QUANT8_ASYMM", 0.04, 0),
+    o2: ("TENSOR_QUANT8_ASYMM", 0.04, 0)
+})
+
+# Instantiate an example
+Example({
+    i2: [
+        8.84, 8.88, 7.41, 5.60, 9.95, 4.37, 0.10, 7.64, 6.50, 9.47,
+        7.55, 3.00, 0.89, 3.01, 6.30, 4.40, 1.64, 6.74, 6.16, 8.60,
+        5.85, 3.17, 7.12, 6.79, 5.77, 6.62, 5.13, 8.44, 5.08, 7.12,
+        2.84, 1.19, 8.37, 0.90, 7.86, 9.69, 1.97, 1.31, 4.42, 9.89,
+        0.18, 9.00, 9.30, 0.44, 5.05, 6.47, 1.09, 9.50, 1.30, 2.18,
+        2.05, 7.74, 7.66, 0.65, 4.18, 7.14, 5.35, 7.90, 1.04, 1.47,
+        9.01, 0.95, 4.07, 0.65,
+        5.47, 2.64, 0.86, 4.86, 2.38, 2.45, 8.77, 0.06, 3.60, 9.28,
+        5.84, 8.97, 6.89, 1.43, 3.90, 5.91, 7.40, 9.25, 3.12, 4.92,
+        1.87, 3.22, 9.50, 6.73, 2.07, 7.30, 3.07, 4.97, 0.24, 8.91,
+        1.09, 0.27, 7.29, 6.94, 2.31, 6.88, 4.33, 1.37, 0.86, 0.46,
+        6.07, 3.81, 0.86, 6.99, 4.36, 1.92, 8.19, 3.57, 7.90, 6.78,
+        4.64, 6.82, 6.18, 9.63, 2.63, 2.33, 1.36, 2.70, 9.99, 9.85,
+        8.06, 4.80, 7.80, 5.43
+    ],
+    roi2: [
+        0, 4, 4, 24, 8,
+        0, 4, 4, 28, 12,
+        1, 7, 1, 25, 11,   # test rounding
+        1, 1, 7,  5, 11    # test roi with shape smaller than output
+    ],
+    o2: [
+        6.16, 8.60, 7.12, 6.79, 5.13, 8.44, 7.86, 9.69, 4.42, 9.89, 9.30, 6.47,
+        7.86, 9.89, 9.30, 9.89, 9.30, 9.50, 7.86, 9.89, 9.30, 9.89, 9.30, 9.50,
+        9.50, 6.73, 9.50, 9.28, 6.89, 8.97, 6.18, 9.63, 9.99, 9.85, 9.99, 9.85,
+        7.29, 6.94, 7.29, 6.94, 2.31, 6.88, 7.90, 6.78, 7.90, 6.82, 4.64, 6.82
+    ]
+}).AddNchw(i2, o2, layout).AddVariations("relaxed", quant8)