| /* | |
| * 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. | |
| */ | |
| #ifndef ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H | |
| #define ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H | |
| #include <chrono> | |
| #include <fstream> | |
| #include <limits> | |
| #include <memory> | |
| #include <random> | |
| #include <sstream> | |
| #include <string> | |
| #include <vector> | |
| #include "RandomGraphGenerator.h" | |
| #include "RandomVariable.h" | |
| #include "TestNeuralNetworksWrapper.h" | |
| namespace android { | |
| namespace nn { | |
| namespace fuzzing_test { | |
| #define NN_FUZZER_LOG_INIT(filename) Logger::get()->init((filename)) | |
| #define NN_FUZZER_LOG_CLOSE Logger::get()->close() | |
| #define NN_FUZZER_LOG \ | |
| if (!Logger::get()->enabled()) \ | |
| ; \ | |
| else \ | |
| LoggerStream(false) << alignedString(__FUNCTION__, 20) | |
| #define NN_FUZZER_CHECK(condition) \ | |
| if ((condition)) \ | |
| ; \ | |
| else \ | |
| LoggerStream(true) << alignedString(__FUNCTION__, 20) << "Check failed " << #condition \ | |
| << ": " | |
| // A Singleton manages the global configurations of logging. | |
| class Logger { | |
| public: | |
| static Logger* get() { | |
| static Logger instance; | |
| return &instance; | |
| } | |
| void init(const std::string& filename) { | |
| os.open(filename); | |
| mStart = std::chrono::high_resolution_clock::now(); | |
| } | |
| bool enabled() { return os.is_open(); } | |
| void close() { | |
| if (os.is_open()) os.close(); | |
| } | |
| void log(const std::string& str) { | |
| if (os.is_open()) os << getElapsedTime() << str << std::flush; | |
| } | |
| private: | |
| Logger() = default; | |
| Logger(const Logger&) = delete; | |
| Logger& operator=(const Logger&) = delete; | |
| std::string getElapsedTime(); | |
| std::ofstream os; | |
| std::chrono::time_point<std::chrono::high_resolution_clock> mStart; | |
| }; | |
| // Controls logging of a single line. | |
| class LoggerStream { | |
| public: | |
| LoggerStream(bool abortAfterLog) : mAbortAfterLog(abortAfterLog) {} | |
| ~LoggerStream() { | |
| Logger::get()->log(ss.str() + '\n'); | |
| if (mAbortAfterLog) { | |
| std::cout << ss.str() << std::endl; | |
| abort(); | |
| } | |
| } | |
| template <typename T> | |
| LoggerStream& operator<<(const T& str) { | |
| ss << str; | |
| return *this; | |
| } | |
| private: | |
| LoggerStream(const LoggerStream&) = delete; | |
| LoggerStream& operator=(const LoggerStream&) = delete; | |
| std::stringstream ss; | |
| bool mAbortAfterLog; | |
| }; | |
| template <typename T> | |
| inline std::string toString(const T& obj) { | |
| return std::to_string(obj); | |
| } | |
| template <typename T> | |
| inline std::string joinStr(const std::string& joint, const std::vector<T>& items) { | |
| std::stringstream ss; | |
| for (uint32_t i = 0; i < items.size(); i++) { | |
| if (i == 0) { | |
| ss << toString(items[i]); | |
| } else { | |
| ss << joint << toString(items[i]); | |
| } | |
| } | |
| return ss.str(); | |
| } | |
| template <typename T, class Function> | |
| inline std::string joinStr(const std::string& joint, const std::vector<T>& items, Function str) { | |
| std::stringstream ss; | |
| for (uint32_t i = 0; i < items.size(); i++) { | |
| if (i != 0) ss << joint; | |
| ss << str(items[i]); | |
| } | |
| return ss.str(); | |
| } | |
| template <typename T> | |
| inline std::string joinStr(const std::string& joint, int limit, const std::vector<T>& items) { | |
| if (items.size() > static_cast<size_t>(limit)) { | |
| std::vector<T> topMax(items.begin(), items.begin() + limit); | |
| return joinStr(joint, topMax) + ", (" + toString(items.size() - limit) + " ommited), " + | |
| toString(items.back()); | |
| } else { | |
| return joinStr(joint, items); | |
| } | |
| } | |
| static const char* kOperationNames[] = { | |
| "ADD", | |
| "AVERAGE_POOL_2D", | |
| "CONCATENATION", | |
| "CONV_2D", | |
| "DEPTHWISE_CONV_2D", | |
| "DEPTH_TO_SPACE", | |
| "DEQUANTIZE", | |
| "EMBEDDING_LOOKUP", | |
| "FLOOR", | |
| "FULLY_CONNECTED", | |
| "HASHTABLE_LOOKUP", | |
| "L2_NORMALIZATION", | |
| "L2_POOL", | |
| "LOCAL_RESPONSE_NORMALIZATION", | |
| "LOGISTIC", | |
| "LSH_PROJECTION", | |
| "LSTM", | |
| "MAX_POOL_2D", | |
| "MUL", | |
| "RELU", | |
| "RELU1", | |
| "RELU6", | |
| "RESHAPE", | |
| "RESIZE_BILINEAR", | |
| "RNN", | |
| "SOFTMAX", | |
| "SPACE_TO_DEPTH", | |
| "SVDF", | |
| "TANH", | |
| "BATCH_TO_SPACE_ND", | |
| "DIV", | |
| "MEAN", | |
| "PAD", | |
| "SPACE_TO_BATCH_ND", | |
| "SQUEEZE", | |
| "STRIDED_SLICE", | |
| "SUB", | |
| "TRANSPOSE", | |
| "ABS", | |
| "ARGMAX", | |
| "ARGMIN", | |
| "AXIS_ALIGNED_BBOX_TRANSFORM", | |
| "BIDIRECTIONAL_SEQUENCE_LSTM", | |
| "BIDIRECTIONAL_SEQUENCE_RNN", | |
| "BOX_WITH_NMS_LIMIT", | |
| "CAST", | |
| "CHANNEL_SHUFFLE", | |
| "DETECTION_POSTPROCESSING", | |
| "EQUAL", | |
| "EXP", | |
| "EXPAND_DIMS", | |
| "GATHER", | |
| "GENERATE_PROPOSALS", | |
| "GREATER", | |
| "GREATER_EQUAL", | |
| "GROUPED_CONV_2D", | |
| "HEATMAP_MAX_KEYPOINT", | |
| "INSTANCE_NORMALIZATION", | |
| "LESS", | |
| "LESS_EQUAL", | |
| "LOG", | |
| "LOGICAL_AND", | |
| "LOGICAL_NOT", | |
| "LOGICAL_OR", | |
| "LOG_SOFTMAX", | |
| "MAXIMUM", | |
| "MINIMUM", | |
| "NEG", | |
| "NOT_EQUAL", | |
| "PAD_V2", | |
| "POW", | |
| "PRELU", | |
| "QUANTIZE", | |
| "QUANTIZED_16BIT_LSTM", | |
| "RANDOM_MULTINOMIAL", | |
| "REDUCE_ALL", | |
| "REDUCE_ANY", | |
| "REDUCE_MAX", | |
| "REDUCE_MIN", | |
| "REDUCE_PROD", | |
| "REDUCE_SUM", | |
| "ROI_ALIGN", | |
| "ROI_POOLING", | |
| "RSQRT", | |
| "SELECT", | |
| "SIN", | |
| "SLICE", | |
| "SPLIT", | |
| "SQRT", | |
| "TILE", | |
| "TOPK_V2", | |
| "TRANSPOSE_CONV_2D", | |
| "UNIDIRECTIONAL_SEQUENCE_LSTM", | |
| "UNIDIRECTIONAL_SEQUENCE_RNN", | |
| "RESIZE_NEAREST_NEIGHBOR", | |
| }; | |
| static const char* kTypeNames[] = { | |
| "FLOAT32", | |
| "INT32", | |
| "UINT32", | |
| "TENSOR_FLOAT32", | |
| "TENSOR_INT32", | |
| "TENSOR_QUANT8_ASYMM", | |
| "BOOL", | |
| "TENSOR_QUANT16_SYMM", | |
| "TENSOR_FLOAT16", | |
| "TENSOR_BOOL8", | |
| "FLOAT16", | |
| "TENSOR_QUANT8_SYMM_PER_CHANNEL", | |
| "TENSOR_QUANT16_ASYMM", | |
| "TENSOR_QUANT8_SYMM", | |
| "TENSOR_QUANT8_ASYMM_SIGNED", | |
| }; | |
| static const char* kLifeTimeNames[6] = { | |
| "TEMPORARY_VARIABLE", "SUBGRAPH_INPUT", "SUBGRAPH_OUTPUT", | |
| "CONSTANT_COPY", "CONSTANT_REFERENCE", "NO_VALUE", | |
| }; | |
| static const bool kScalarDataType[]{ | |
| true, // ANEURALNETWORKS_FLOAT32 | |
| true, // ANEURALNETWORKS_INT32 | |
| true, // ANEURALNETWORKS_UINT32 | |
| false, // ANEURALNETWORKS_TENSOR_FLOAT32 | |
| false, // ANEURALNETWORKS_TENSOR_INT32 | |
| false, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM | |
| true, // ANEURALNETWORKS_BOOL | |
| false, // ANEURALNETWORKS_TENSOR_QUANT16_SYMM | |
| false, // ANEURALNETWORKS_TENSOR_FLOAT16 | |
| false, // ANEURALNETWORKS_TENSOR_BOOL8 | |
| true, // ANEURALNETWORKS_FLOAT16 | |
| false, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL | |
| false, // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM | |
| false, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM | |
| false, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED | |
| }; | |
| static const uint32_t kSizeOfDataType[]{ | |
| 4, // ANEURALNETWORKS_FLOAT32 | |
| 4, // ANEURALNETWORKS_INT32 | |
| 4, // ANEURALNETWORKS_UINT32 | |
| 4, // ANEURALNETWORKS_TENSOR_FLOAT32 | |
| 4, // ANEURALNETWORKS_TENSOR_INT32 | |
| 1, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM | |
| 1, // ANEURALNETWORKS_BOOL | |
| 2, // ANEURALNETWORKS_TENSOR_QUANT16_SYMM | |
| 2, // ANEURALNETWORKS_TENSOR_FLOAT16 | |
| 1, // ANEURALNETWORKS_TENSOR_BOOL8 | |
| 2, // ANEURALNETWORKS_FLOAT16 | |
| 1, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL | |
| 2, // ANEURALNETWORKS_TENSOR_QUANT16_ASYMM | |
| 1, // ANEURALNETWORKS_TENSOR_QUANT8_SYMM | |
| 1, // ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED | |
| }; | |
| template <> | |
| inline std::string toString<RandomVariableType>(const RandomVariableType& type) { | |
| static const std::string typeNames[] = {"FREE", "CONST", "OP"}; | |
| return typeNames[static_cast<int>(type)]; | |
| } | |
| inline std::string alignedString(std::string str, int width) { | |
| str.push_back(':'); | |
| str.resize(width + 1, ' '); | |
| return str; | |
| } | |
| template <> | |
| inline std::string toString<RandomVariableRange>(const RandomVariableRange& range) { | |
| return "[" + joinStr(", ", 20, range.getChoices()) + "]"; | |
| } | |
| template <> | |
| inline std::string toString<RandomOperandType>(const RandomOperandType& type) { | |
| static const std::string typeNames[] = {"Input", "Output", "Internal", "Parameter"}; | |
| return typeNames[static_cast<int>(type)]; | |
| } | |
| template <> | |
| inline std::string toString<RandomVariableNode>(const RandomVariableNode& var) { | |
| std::stringstream ss; | |
| ss << "var" << var->index << " = "; | |
| switch (var->type) { | |
| case RandomVariableType::FREE: | |
| ss << "FREE " << toString(var->range); | |
| break; | |
| case RandomVariableType::CONST: | |
| ss << "CONST " << toString(var->value); | |
| break; | |
| case RandomVariableType::OP: | |
| ss << "var" << var->parent1->index << " " << var->op->getName(); | |
| if (var->parent2 != nullptr) ss << " var" << var->parent2->index; | |
| ss << ", " << toString(var->range); | |
| break; | |
| default: | |
| NN_FUZZER_CHECK(false); | |
| } | |
| ss << ", timestamp = " << var->timestamp; | |
| return ss.str(); | |
| } | |
| template <> | |
| inline std::string toString<Type>(const Type& type) { | |
| return kTypeNames[static_cast<int32_t>(type)]; | |
| } | |
| template <> | |
| inline std::string toString<RandomVariable>(const RandomVariable& var) { | |
| return "var" + std::to_string(var.get()->index); | |
| } | |
| template <> | |
| inline std::string toString<RandomOperand>(const RandomOperand& op) { | |
| return toString(op.type) + ", dimension = [" + | |
| joinStr(", ", op.dimensions, | |
| [](const RandomVariable& var) { return std::to_string(var.getValue()); }) + | |
| "], scale = " + toString(op.scale) + " , zero_point = " + toString(op.zeroPoint); | |
| } | |
| // This class is a workaround for two issues our code relies on: | |
| // 1. sizeof(bool) is implementation defined. | |
| // 2. vector<bool> does not allow direct pointer access via the data() method. | |
| class bool8 { | |
| public: | |
| bool8() : mValue() {} | |
| /* implicit */ bool8(bool value) : mValue(value) {} | |
| inline operator bool() const { return mValue != 0; } | |
| private: | |
| uint8_t mValue; | |
| }; | |
| static_assert(sizeof(bool8) == 1, "size of bool8 must be 8 bits"); | |
| // Dump the random graph to a spec file. | |
| class SpecWriter { | |
| public: | |
| SpecWriter(std::string filename, std::string testname = ""); | |
| bool isOpen() { return os.is_open(); } | |
| void dump(const std::vector<RandomOperation>& operations, | |
| const std::vector<std::shared_ptr<RandomOperand>>& operands); | |
| private: | |
| void dump(test_wrapper::Type type, const uint8_t* buffer, uint32_t length); | |
| void dump(const std::vector<RandomVariable>& dimensions); | |
| void dump(const std::shared_ptr<RandomOperand>& op); | |
| void dump(const RandomOperation& op); | |
| template <typename T> | |
| void dump(const T* buffer, uint32_t length) { | |
| for (uint32_t i = 0; i < length; i++) { | |
| if (i != 0) os << ", "; | |
| if constexpr (std::is_integral<T>::value) { | |
| os << static_cast<int>(buffer[i]); | |
| } else if constexpr (std::is_same<T, _Float16>::value) { | |
| os << static_cast<float>(buffer[i]); | |
| } else if constexpr (std::is_same<T, bool8>::value) { | |
| os << (buffer[i] ? "True" : "False"); | |
| } else { | |
| os << buffer[i]; | |
| } | |
| } | |
| } | |
| std::ofstream os; | |
| }; | |
| struct RandomNumberGenerator { | |
| static std::mt19937 generator; | |
| }; | |
| inline bool getBernoulli(double p) { | |
| std::bernoulli_distribution dis(p); | |
| return dis(RandomNumberGenerator::generator); | |
| } | |
| template <typename T> | |
| inline constexpr bool nnIsFloat = std::is_floating_point_v<T> || std::is_same_v<T, _Float16>; | |
| // getUniform for floating point values operates on a open interval (lower, upper). | |
| // This is important for generating a scale that is greater than but not equal to a lower bound. | |
| template <typename T> | |
| inline std::enable_if_t<nnIsFloat<T>, T> getUniform(T lower, T upper) { | |
| float nextLower = std::nextafter(static_cast<float>(lower), std::numeric_limits<float>::max()); | |
| std::uniform_real_distribution<float> dis(nextLower, upper); | |
| return dis(RandomNumberGenerator::generator); | |
| } | |
| // getUniform for integers operates on a closed interval [lower, upper]. | |
| // This is important that 255 should be included as a valid candidate for QUANT8_ASYMM values. | |
| template <typename T> | |
| inline std::enable_if_t<std::is_integral_v<T>, T> getUniform(T lower, T upper) { | |
| std::uniform_int_distribution<T> dis(lower, upper); | |
| return dis(RandomNumberGenerator::generator); | |
| } | |
| template <typename T> | |
| inline const T& getRandomChoice(const std::vector<T>& choices) { | |
| NN_FUZZER_CHECK(!choices.empty()) << "Empty choices!"; | |
| std::uniform_int_distribution<size_t> dis(0, choices.size() - 1); | |
| size_t i = dis(RandomNumberGenerator::generator); | |
| return choices[i]; | |
| } | |
| template <typename T> | |
| inline void randomShuffle(std::vector<T>* vec) { | |
| std::shuffle(vec->begin(), vec->end(), RandomNumberGenerator::generator); | |
| } | |
| } // namespace fuzzing_test | |
| } // namespace nn | |
| } // namespace android | |
| #endif // ANDROID_FRAMEWORKS_ML_NN_RUNTIME_TEST_FUZZING_RANDOM_GRAPH_GENERATOR_UTILS_H |