In TensorFlow GraphDef, when a node has multiple outputs, they are named name:0, name:1, ... where 'name' is the node's name(). Just 'name' is an equivalent shorthand form for name:0. A TensorFlow GraphDef does not explicitly list all the outputs of each node (unlike inputs), it being implied by the node's name and operator type (the latter implies the number of outputs). This makes it non-trivial
| 2146 | // at least each node lists explicitly its inputs, so after we've loaded |
| 2147 | // all nodes, we can use that information. |
| 2148 | void AddExtraOutputs(Model* model) { |
| 2149 | // Construct the list of all arrays consumed by anything in the graph. |
| 2150 | std::vector<string> consumed_arrays; |
| 2151 | // Add arrays consumed by an op. |
| 2152 | for (const auto& consumer_op : model->operators) { |
| 2153 | for (const string& input : consumer_op->inputs) { |
| 2154 | consumed_arrays.push_back(input); |
| 2155 | } |
| 2156 | } |
| 2157 | // Add global outputs of the model. |
| 2158 | for (const string& output_array : model->flags.output_arrays()) { |
| 2159 | consumed_arrays.push_back(output_array); |
| 2160 | } |
| 2161 | // Add arrays consumed by a RNN back-edge. |
| 2162 | for (const auto& rnn_state : model->flags.rnn_states()) { |
| 2163 | consumed_arrays.push_back(rnn_state.back_edge_source_array()); |
| 2164 | } |
| 2165 | // Now add operator outputs so that all arrays that are consumed, |
| 2166 | // are produced. |
| 2167 | for (const string& consumed_array : consumed_arrays) { |
| 2168 | // Test if consumed_array is already the output of some op. |
| 2169 | // This has occurred in a model where separate nodes had names of the form |
| 2170 | // foo:$i with the same base name foo. |
| 2171 | if (GetOpWithOutput(*model, consumed_array)) { |
| 2172 | continue; |
| 2173 | } |
| 2174 | // Split the consumed array name into the form name:output_index. |
| 2175 | const std::vector<string>& split = absl::StrSplit(consumed_array, ':'); |
| 2176 | // If not of the form name:output_index, then this is not an additional |
| 2177 | // output of a node with multiple outputs, so nothing to do here. |
| 2178 | if (split.size() != 2) { |
| 2179 | continue; |
| 2180 | } |
| 2181 | int output_index = 0; |
| 2182 | if (!absl::SimpleAtoi(split[1], &output_index)) { |
| 2183 | continue; |
| 2184 | } |
| 2185 | // Each op is initially recorded as producing at least the array that |
| 2186 | // has its name. We use that to identify the producer node. |
| 2187 | auto* producer_op = GetOpWithOutput(*model, split[0]); |
| 2188 | if (!producer_op) { |
| 2189 | continue; |
| 2190 | } |
| 2191 | // Add extra outputs to that producer node, all the way to the |
| 2192 | // output_index. |
| 2193 | while (producer_op->outputs.size() <= output_index) { |
| 2194 | using toco::port::StringF; |
| 2195 | producer_op->outputs.push_back( |
| 2196 | StringF("%s:%d", split[0], producer_op->outputs.size())); |
| 2197 | } |
| 2198 | } |
| 2199 | } |
| 2200 | |
| 2201 | bool InlineAllFunctions(GraphDef* graphdef) { |
| 2202 | if (graphdef->library().function().empty()) { |
no test coverage detected