1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define LOG_TAG "GraphDump" 18 19 #include "GraphDump.h" 20 21 #include "HalInterfaces.h" 22 23 #include <android-base/logging.h> 24 #include <set> 25 #include <iostream> 26 #include <sstream> 27 28 namespace android { 29 namespace nn { 30 31 // class Dumper is a wrapper around an std::ostream (if instantiated 32 // with a pointer to a stream) or around LOG(INFO) (otherwise). 33 // 34 // Send fragments of output to it with operator<<(), as per usual 35 // stream conventions. Unlike with LOG(INFO), there is no implicit 36 // end-of-line. To end a line, send Dumper::endl. 37 // 38 // Example: 39 // 40 // Dumper d(nullptr); // will go to LOG(INFO) 41 // d << "These words are"; 42 // d << " all" << " on"; 43 // d << " the same line." << Dumper::endl; 44 // 45 namespace { 46 class Dumper { 47 public: 48 Dumper(std::ostream* outStream) : mStream(outStream) { } 49 50 Dumper(const Dumper&) = delete; 51 void operator=(const Dumper&) = delete; 52 53 template <typename T> 54 Dumper& operator<<(const T& val) { 55 mStringStream << val; 56 return *this; 57 } 58 59 class EndlType { }; 60 61 Dumper& operator<<(EndlType) { 62 if (mStream) { 63 *mStream << mStringStream.str() << std::endl; 64 } else { 65 // TODO: There is a limit of how long a single LOG line 66 // can be; extra characters are truncated. (See 67 // LOGGER_ENTRY_MAX_PAYLOAD and LOGGER_ENTRY_MAX_LEN.) We 68 // may want to figure out the linebreak rules for the .dot 69 // format and try to ensure that we generate correct .dot 70 // output whose lines do not exceed some maximum length. 71 // The intelligence for breaking the lines might have to 72 // live in graphDump() rather than in the Dumper class, so 73 // that it can be sensitive to the .dot format. 74 LOG(INFO) << mStringStream.str(); 75 } 76 std::ostringstream empty; 77 std::swap(mStringStream, empty); 78 return *this; 79 } 80 81 static const EndlType endl; 82 private: 83 std::ostream* mStream; 84 std::ostringstream mStringStream; 85 }; 86 87 const Dumper::EndlType Dumper::endl; 88 } 89 90 91 // Provide short name for OperandType value. 92 static std::string translate(OperandType type) { 93 switch (type) { 94 case OperandType::FLOAT32: return "F32"; 95 case OperandType::INT32: return "I32"; 96 case OperandType::UINT32: return "U32"; 97 case OperandType::TENSOR_FLOAT32: return "TF32"; 98 case OperandType::TENSOR_INT32: return "TI32"; 99 case OperandType::TENSOR_QUANT8_ASYMM: return "TQ8A"; 100 case OperandType::OEM: return "OEM"; 101 case OperandType::TENSOR_OEM_BYTE: return "TOEMB"; 102 default: return toString(type); 103 } 104 } 105 106 // If the specified Operand of the specified Model has OperandType 107 // nnType corresponding to C++ type cppType and is of 108 // OperandLifeTime::CONSTANT_COPY, then write the Operand's value to 109 // the Dumper. 110 namespace { 111 template<OperandType nnType, typename cppType> 112 void tryValueDump(Dumper& dump, const Model& model, const Operand& opnd) { 113 if (opnd.type != nnType || 114 opnd.lifetime != OperandLifeTime::CONSTANT_COPY || 115 opnd.location.length != sizeof(cppType)) { 116 return; 117 } 118 119 cppType val; 120 memcpy(&val, &model.operandValues[opnd.location.offset], sizeof(cppType)); 121 dump << " = " << val; 122 } 123 } 124 125 void graphDump(const char* name, const Model& model, std::ostream* outStream) { 126 // Operand nodes are named "d" (operanD) followed by operand index. 127 // Operation nodes are named "n" (operatioN) followed by operation index. 128 // (These names are not the names that are actually displayed -- those 129 // names are given by the "label" attribute.) 130 131 Dumper dump(outStream); 132 133 dump << "// " << name << Dumper::endl; 134 dump << "digraph {" << Dumper::endl; 135 136 // model inputs and outputs 137 std::set<uint32_t> modelIO; 138 for (unsigned i = 0, e = model.inputIndexes.size(); i < e; i++) { 139 modelIO.insert(model.inputIndexes[i]); 140 } 141 for (unsigned i = 0, e = model.outputIndexes.size(); i < e; i++) { 142 modelIO.insert(model.outputIndexes[i]); 143 } 144 145 // model operands 146 for (unsigned i = 0, e = model.operands.size(); i < e; i++) { 147 dump << " d" << i << " ["; 148 if (modelIO.count(i)) { 149 dump << "style=filled fillcolor=black fontcolor=white "; 150 } 151 dump << "label=\"" << i; 152 const Operand& opnd = model.operands[i]; 153 const char* kind = nullptr; 154 switch (opnd.lifetime) { 155 case OperandLifeTime::CONSTANT_COPY: 156 kind = "COPY"; 157 break; 158 case OperandLifeTime::CONSTANT_REFERENCE: 159 kind = "REF"; 160 break; 161 case OperandLifeTime::NO_VALUE: 162 kind = "NO"; 163 break; 164 default: 165 // nothing interesting 166 break; 167 } 168 if (kind) { 169 dump << ": " << kind; 170 } 171 dump << "\\n" << translate(opnd.type); 172 tryValueDump<OperandType::FLOAT32, float>(dump, model, opnd); 173 tryValueDump<OperandType::INT32, int>(dump, model, opnd); 174 tryValueDump<OperandType::UINT32, unsigned>(dump, model, opnd); 175 if (opnd.dimensions.size()) { 176 dump << "("; 177 for (unsigned i = 0, e = opnd.dimensions.size(); i < e; i++) { 178 if (i > 0) { 179 dump << "x"; 180 } 181 dump << opnd.dimensions[i]; 182 } 183 dump << ")"; 184 } 185 dump << "\"]" << Dumper::endl; 186 } 187 188 // model operations 189 for (unsigned i = 0, e = model.operations.size(); i < e; i++) { 190 const Operation& operation = model.operations[i]; 191 dump << " n" << i << " [shape=box"; 192 const uint32_t maxArity = std::max(operation.inputs.size(), operation.outputs.size()); 193 if (maxArity > 1) { 194 if (maxArity == operation.inputs.size()) { 195 dump << " ordering=in"; 196 } else { 197 dump << " ordering=out"; 198 } 199 } 200 dump << " label=\"" << i << ": " 201 << toString(operation.type) << "\"]" << Dumper::endl; 202 { 203 // operation inputs 204 for (unsigned in = 0, inE = operation.inputs.size(); in < inE; in++) { 205 dump << " d" << operation.inputs[in] << " -> n" << i; 206 if (inE > 1) { 207 dump << " [label=" << in << "]"; 208 } 209 dump << Dumper::endl; 210 } 211 } 212 213 { 214 // operation outputs 215 for (unsigned out = 0, outE = operation.outputs.size(); out < outE; out++) { 216 dump << " n" << i << " -> d" << operation.outputs[out]; 217 if (outE > 1) { 218 dump << " [label=" << out << "]"; 219 } 220 dump << Dumper::endl; 221 } 222 } 223 } 224 dump << "}" << Dumper::endl; 225 } 226 227 } // namespace nn 228 } // namespace android 229