/* * Copyright (C) 2019 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 "fuzzing/operation_signatures/OperationSignatureUtils.h" namespace android { namespace nn { namespace fuzzing_test { static void embeddingLookupConstructor(Type, uint32_t rank, RandomOperation* op) { setFreeDimensions(op->inputs[0], /*rank=*/1); setFreeDimensions(op->inputs[1], rank); op->outputs[0]->dimensions.resize(rank); op->outputs[0]->dimensions[0] = op->inputs[0]->dimensions[0]; for (uint32_t i = 1; i < rank; i++) { op->outputs[0]->dimensions[i] = op->inputs[1]->dimensions[i]; } setSameQuantization(op->outputs[0], op->inputs[1]); } static void embeddingLookupFinalizer(RandomOperation* op) { uint32_t dimValue = op->inputs[1]->dimensions[0].getValue(); uint32_t numElements = op->inputs[0]->getNumberOfElements(); for (uint32_t i = 0; i < numElements; i++) { // The index values must be in the range of [0, input1_dim0). op->inputs[0]->value<int32_t>(i) = getUniform<int32_t>(0, dimValue - 1); } } DEFINE_OPERATION_SIGNATURE(EMBEDDING_LOOKUP_V1_0){ .opType = ANEURALNETWORKS_EMBEDDING_LOOKUP, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {2, 3, 4}, .version = HalVersion::V1_0, .inputs = {PARAMETER_NONE(Type::TENSOR_INT32), INPUT_DEFAULT}, .outputs = {OUTPUT_DEFAULT}, .constructor = embeddingLookupConstructor, .finalizer = embeddingLookupFinalizer}; static void hashtableLookupConstructor(Type, uint32_t rank, RandomOperation* op) { op->inputs[0]->dimensions = {RandomVariableType::FREE}; op->inputs[1]->dimensions = {RandomVariableType::FREE}; op->inputs[2]->dimensions.resize(rank); op->outputs[0]->dimensions.resize(rank); op->inputs[2]->dimensions[0] = op->inputs[1]->dimensions[0]; op->outputs[0]->dimensions[0] = op->inputs[0]->dimensions[0]; for (uint32_t i = 1; i < rank; i++) { op->inputs[2]->dimensions[i] = RandomVariableType::FREE; op->outputs[0]->dimensions[i] = op->inputs[2]->dimensions[i]; } setSameQuantization(op->outputs[0], op->inputs[2]); op->outputs[1]->dimensions = {op->inputs[0]->dimensions[0]}; } static void hashtableLookupFinalizer(RandomOperation* op) { // Generate values for keys. The keys tensor must be sorted in ascending order. uint32_t n = op->inputs[1]->getNumberOfElements(); int32_t val = 0; for (uint32_t i = 0; i < n; i++) { op->inputs[1]->value<int32_t>(i) = val; val += getUniform<int32_t>(1, 2); } // Generate values for lookups. uint32_t k = op->inputs[0]->getNumberOfElements(); for (uint32_t i = 0; i < k; i++) { op->inputs[0]->value<int32_t>(i) = getUniform<int32_t>(0, val); } } // The hits tensor in HASHTABLE_LOOKUP. static const OperandSignature hitsTensor_HASHTABLE_LOOKUP = { .type = RandomOperandType::OUTPUT, .constructor = [](Type, uint32_t, RandomOperand* op) { op->dataType = Type::TENSOR_QUANT8_ASYMM; op->scale = 1.0f; op->zeroPoint = 0; }}; DEFINE_OPERATION_SIGNATURE(HASHTABLE_LOOKUP_V1_0){ .opType = ANEURALNETWORKS_HASHTABLE_LOOKUP, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {2, 3, 4}, .version = HalVersion::V1_0, .inputs = {PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32), INPUT_DEFAULT}, .outputs = {OUTPUT_DEFAULT, hitsTensor_HASHTABLE_LOOKUP}, .constructor = hashtableLookupConstructor, .finalizer = hashtableLookupFinalizer}; static void gatherConstructor(Type, uint32_t rank, RandomOperation* op) { // Generate value for "axis" scalar. int32_t axis = getUniform<int32_t>(-rank, rank - 1); op->inputs[1]->setScalarValue<int32_t>(axis); if (axis < 0) axis += rank; // Set dimensions for input and indices tensor. uint32_t indRank = getUniform<uint32_t>(1, 5); setFreeDimensions(op->inputs[0], rank); setFreeDimensions(op->inputs[2], indRank); for (uint32_t i = 0; i < static_cast<uint32_t>(axis); i++) { op->outputs[0]->dimensions.push_back(op->inputs[0]->dimensions[i]); } for (uint32_t i = 0; i < indRank; i++) { op->outputs[0]->dimensions.push_back(op->inputs[2]->dimensions[i]); } for (uint32_t i = axis + 1; i < rank; i++) { op->outputs[0]->dimensions.push_back(op->inputs[0]->dimensions[i]); } setSameQuantization(op->outputs[0], op->inputs[0]); } static void gatherFinalizer(RandomOperation* op) { int32_t axis = op->inputs[1]->value<int32_t>(); if (axis < 0) axis += op->inputs[0]->dimensions.size(); uint32_t dimValue = op->inputs[0]->dimensions[axis].getValue(); uint32_t numElements = op->inputs[2]->getNumberOfElements(); for (uint32_t i = 0; i < numElements; i++) { // The index values must be in the range of [0, dimValue). op->inputs[2]->value<int32_t>(i) = getUniform<int32_t>(0, dimValue - 1); } } DEFINE_OPERATION_SIGNATURE(GATHER_V1_2){ .opType = ANEURALNETWORKS_GATHER, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_FLOAT16, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {1, 2, 3, 4, 5}, .version = HalVersion::V1_2, .inputs = {INPUT_DEFAULT, PARAMETER_NONE(Type::INT32), PARAMETER_NONE(Type::TENSOR_INT32)}, .outputs = {OUTPUT_DEFAULT}, .constructor = gatherConstructor, .finalizer = gatherFinalizer}; static void selectConstructor(Type, uint32_t rank, RandomOperation* op) { setFreeDimensions(op->inputs[0], rank); op->inputs[1]->dimensions = op->inputs[0]->dimensions; op->inputs[2]->dimensions = op->inputs[0]->dimensions; op->outputs[0]->dimensions = op->inputs[0]->dimensions; setSameQuantization(op->inputs[2], op->inputs[1]); setSameQuantization(op->outputs[0], op->inputs[1]); } DEFINE_OPERATION_SIGNATURE(SELECT_V1_2){ .opType = ANEURALNETWORKS_SELECT, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_FLOAT16, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {1, 2, 3, 4}, .version = HalVersion::V1_2, .inputs = {INPUT_TYPED(Type::TENSOR_BOOL8), INPUT_DEFAULT, INPUT_DEFAULT}, .outputs = {OUTPUT_DEFAULT}, .constructor = selectConstructor}; static void topKConstructor(Type, uint32_t rank, RandomOperation* op) { setFreeDimensions(op->inputs[0], rank); op->outputs[0]->dimensions.resize(rank); op->outputs[1]->dimensions.resize(rank); for (uint32_t i = 0; i < rank - 1; i++) { op->outputs[0]->dimensions[i] = op->inputs[0]->dimensions[i]; op->outputs[1]->dimensions[i] = op->inputs[0]->dimensions[i]; } // K must be in the range of [1, depth]. auto k = op->inputs[1]->value<RandomVariable>(); k.setRange(1, kInvalidValue); op->inputs[0]->dimensions.back().setGreaterEqual(k); op->outputs[0]->dimensions.back() = k; op->outputs[1]->dimensions.back() = k; setSameQuantization(op->outputs[0], op->inputs[0]); // As the sorting is not required to be stable, we should not check the second output (indices). op->outputs[1]->doNotCheckAccuracy = true; op->outputs[1]->doNotConnect = true; } DEFINE_OPERATION_SIGNATURE(TOPK_V2_V1_2){ .opType = ANEURALNETWORKS_TOPK_V2, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_FLOAT16, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {1, 2, 3, 4}, .version = HalVersion::V1_2, .inputs = {INPUT_DEFAULT, RANDOM_INT_FREE}, .outputs = {OUTPUT_DEFAULT, OUTPUT_TYPED(Type::TENSOR_INT32)}, .constructor = topKConstructor}; static void sliceConstructor(Type, uint32_t rank, RandomOperation* op) { op->inputs[1]->dimensions = {rank}; op->inputs[2]->dimensions = {rank}; setFreeDimensions(op->inputs[0], rank); setFreeDimensions(op->outputs[0], rank); // The axis size of output must be less than or equal to input. for (uint32_t i = 0; i < rank; i++) { op->inputs[0]->dimensions[i].setGreaterEqual(op->outputs[0]->dimensions[i]); } setSameQuantization(op->outputs[0], op->inputs[0]); } static void sliceFinalizer(RandomOperation* op) { uint32_t rank = op->inputs[0]->dimensions.size(); int32_t* begin = reinterpret_cast<int32_t*>(op->inputs[1]->buffer.data()); int32_t* size = reinterpret_cast<int32_t*>(op->inputs[2]->buffer.data()); for (uint32_t i = 0; i < rank; i++) { int32_t inputSize = op->inputs[0]->dimensions[i].getValue(); int32_t outputSize = op->outputs[0]->dimensions[i].getValue(); // Randomly choose a valid begin index for each axis. begin[i] = getUniform<int32_t>(0, inputSize - outputSize); size[i] = outputSize; } } DEFINE_OPERATION_SIGNATURE(SLICE_V1_2){ .opType = ANEURALNETWORKS_SLICE, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_FLOAT16, Type::TENSOR_INT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {1, 2, 3, 4}, .version = HalVersion::V1_2, .inputs = {INPUT_DEFAULT, PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32)}, .outputs = {OUTPUT_DEFAULT}, .constructor = sliceConstructor, .finalizer = sliceFinalizer}; inline int32_t convertToBitMask(const std::vector<bool>& flags) { int32_t mask = 0, bit = 1; for (bool flag : flags) { if (flag) mask |= bit; bit <<= 1; } return mask; } static void stridedSliceConstructor(Type, uint32_t rank, RandomOperation* op) { op->inputs[1]->dimensions = {rank}; op->inputs[2]->dimensions = {rank}; op->inputs[3]->dimensions = {rank}; op->inputs[3]->resizeBuffer<int32_t>(rank); setFreeDimensions(op->inputs[0], rank); std::vector<bool> shrinkMask(rank, false); for (uint32_t i = 0; i < rank; i++) { // TODO: Currently shrinkMask is always set to false. shrinkMask[i] = false; int32_t stride = getUniform<int32_t>(1, 3); op->inputs[3]->value<int32_t>(i) = stride; if (!shrinkMask[i]) { op->outputs[0]->dimensions.push_back(RandomVariableType::FREE); auto maxOut = (op->inputs[0]->dimensions[i] + (stride - 1)) / stride; maxOut.setGreaterEqual(op->outputs[0]->dimensions.back()); } } setSameQuantization(op->outputs[0], op->inputs[0]); op->inputs[6]->setScalarValue<int32_t>(convertToBitMask(shrinkMask)); } static void stridedSliceFinalizer(RandomOperation* op) { uint32_t rank = op->inputs[0]->dimensions.size(); int32_t* begin = reinterpret_cast<int32_t*>(op->inputs[1]->buffer.data()); int32_t* end = reinterpret_cast<int32_t*>(op->inputs[2]->buffer.data()); std::vector<bool> beginMask(rank, false), endMask(rank, false); int32_t shrinkMask = op->inputs[6]->value<int32_t>(); for (uint32_t i = 0, o = 0; i < rank; i++) { int32_t inputSize = op->inputs[0]->dimensions[i].getValue(); int32_t stride = op->inputs[3]->value<int32_t>(i); if ((shrinkMask & (1 << i)) == 0) { int32_t outputSize = op->outputs[0]->dimensions[o++].getValue(); int32_t maxStart = inputSize - (outputSize - 1) * stride - 1; begin[i] = getUniform<int32_t>(0, maxStart); int32_t minEnd = begin[i] + (outputSize - 1) * stride + 1; int32_t maxEnd = std::min(begin[i] + outputSize * stride, inputSize); end[i] = getUniform<int32_t>(minEnd, maxEnd); // Switch to masked begin/end. beginMask[i] = (begin[i] == 0 && getBernoulli(0.2f)); endMask[i] = (end[i] == 0 && getBernoulli(0.2f)); // When begin or end mask is set, begin[i] or end[i] is ignored and can have any // arbitrary value. if (beginMask[i]) begin[i] = getUniform<int32_t>(-inputSize, inputSize - 1); if (endMask[i]) end[i] = getUniform<int32_t>(-inputSize, inputSize - 1); } else { // When shrink mask is set, the begin and end must define a slice of size 1, e.g. // begin[i] = x, end[i] = x + 1. begin[i] = getUniform<int32_t>(0, inputSize - 1); end[i] = begin[i] + 1; } // Switch to negative stride. if (getBernoulli(0.2f)) { op->inputs[3]->value<int32_t>(i) = -stride; std::swap(begin[i], end[i]); std::swap(beginMask[i], endMask[i]); begin[i]--; end[i]--; // end = -1 will be intepreted to inputSize - 1 if not setting endMask. if (end[i] < 0) endMask[i] = true; } } op->inputs[4]->setScalarValue<int32_t>(convertToBitMask(beginMask)); op->inputs[5]->setScalarValue<int32_t>(convertToBitMask(endMask)); } DEFINE_OPERATION_SIGNATURE(STRIDED_SLICE_V1_1){ .opType = ANEURALNETWORKS_STRIDED_SLICE, .supportedDataTypes = {Type::TENSOR_FLOAT32, Type::TENSOR_QUANT8_ASYMM}, .supportedRanks = {1, 2, 3, 4}, .version = HalVersion::V1_1, .inputs = {INPUT_DEFAULT, PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_CHOICE(Type::INT32, 0), PARAMETER_CHOICE(Type::INT32, 0), PARAMETER_CHOICE(Type::INT32, 0)}, .outputs = {OUTPUT_DEFAULT}, .constructor = stridedSliceConstructor, .finalizer = stridedSliceFinalizer}; DEFINE_OPERATION_SIGNATURE(STRIDED_SLICE_V1_2){ .opType = ANEURALNETWORKS_STRIDED_SLICE, .supportedDataTypes = {Type::TENSOR_FLOAT16}, .supportedRanks = {1, 2, 3, 4}, .version = HalVersion::V1_2, .inputs = {INPUT_DEFAULT, PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::TENSOR_INT32), PARAMETER_NONE(Type::INT32), PARAMETER_NONE(Type::INT32), PARAMETER_NONE(Type::INT32)}, .outputs = {OUTPUT_DEFAULT}, .constructor = stridedSliceConstructor, .finalizer = stridedSliceFinalizer}; } // namespace fuzzing_test } // namespace nn } // namespace android