Publishing 2019 R1 content
[platform/upstream/dldt.git] / inference-engine / src / inference_engine / network_serializer.cpp
1 // Copyright (C) 2018-2019 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
3 //
4
5 #include <fstream>
6 #include <map>
7 #include <vector>
8 #include <string>
9
10 #include "details/ie_cnn_network_tools.h"
11 #include "details/caseless.hpp"
12 #include "network_serializer.h"
13 #include "exec_graph_info.hpp"
14 #include "xml_parse_utils.h"
15
16 using namespace InferenceEngine;
17 using namespace details;
18
19 template<typename T> std::string arrayToIRProperty(const T& property) {
20     std::string sProperty;
21     for (size_t i = 0; i < property.size(); i++) {
22         sProperty = sProperty + std::to_string(property[i]) +
23             std::string((i != property.size() - 1) ? "," : "");
24     }
25     return sProperty;
26 }
27
28 template<typename T> std::string arrayRevertToIRProperty(const T& property) {
29     std::string sProperty;
30     for (size_t i = 0; i < property.size(); i++) {
31         sProperty = sProperty + std::to_string(property[property.size() - i - 1]) +
32             std::string((i != property.size() - 1) ? "," : "");
33     }
34     return sProperty;
35 }
36
37
38 void NetworkSerializer::serialize(
39     const std::string &xmlPath,
40     const std::string &binPath,
41     const InferenceEngine::ICNNNetwork& network) {
42     const std::vector<CNNLayerPtr> ordered = CNNNetSortTopologically(network);
43
44     // A flag for serializing executable graph information (not complete IR)
45     bool execGraphInfoSerialization = false;
46     // If first layer has perfCounter parameter set then it's executable graph info serialization.
47     // All other layers must also have this parameter set.
48     if (ordered[0]->params.find(ExecGraphInfoSerialization::PERF_COUNTER) != ordered[0]->params.end()) {
49         execGraphInfoSerialization = true;
50         for (const auto &layer : ordered) {
51             if (layer->params.find(ExecGraphInfoSerialization::PERF_COUNTER) == layer->params.end()) {
52                 THROW_IE_EXCEPTION << "Each node must have " << ExecGraphInfoSerialization::PERF_COUNTER
53                                    << " parameter set in case of executable graph info serialization";
54             }
55         }
56     }
57
58     bool dumpWeights = !execGraphInfoSerialization & !binPath.empty();
59     std::ofstream ofsBin;
60     if (dumpWeights) {
61         ofsBin.open(binPath, std::ofstream::out | std::ofstream::binary);
62         if (!ofsBin) {
63             THROW_IE_EXCEPTION << "File '" << binPath << "' is not opened as out file stream";
64         }
65     }
66
67     pugi::xml_document doc;
68     pugi::xml_node netXml = doc.append_child("net");
69     netXml.append_attribute("name").set_value(network.getName().c_str());
70
71     // no need to print this information for executable graph information serialization because it is not IR.
72     if (!execGraphInfoSerialization) {
73         netXml.append_attribute("version").set_value("3");
74         netXml.append_attribute("batch").set_value(network.getBatchSize());
75     }
76
77     pugi::xml_node layers = netXml.append_child("layers");
78
79     std::map<CNNLayer::Ptr, size_t> matching;
80     for (size_t i = 0; i < ordered.size(); i++) {
81         matching[ordered[i]] = i;
82     }
83
84     const std::string dataName = "data";
85     size_t dataOffset = 0;
86     for (size_t i = 0; i < ordered.size(); ++i) {
87         const CNNLayerPtr node = ordered[i];
88
89         pugi::xml_node layer = layers.append_child("layer");
90         const Precision precision = node->precision;
91         layer.append_attribute("name").set_value(node->name.c_str());
92         layer.append_attribute("type").set_value(node->type.c_str());
93         layer.append_attribute("precision").set_value(precision.name());
94         layer.append_attribute("id").set_value(i);
95
96         if (!execGraphInfoSerialization) {
97             updateStdLayerParams(node);
98         }
99
100         const auto &params = node->params;
101         if (!params.empty()) {
102             pugi::xml_node data = layer.append_child(dataName.c_str());
103
104             for (const auto &it : params) {
105                 data.append_attribute(it.first.c_str()).set_value(it.second.c_str());
106             }
107         }
108
109         if (!node->insData.empty()) {
110             pugi::xml_node input = layer.append_child("input");
111
112             for (size_t iport = 0; iport < node->insData.size(); iport++) {
113                 const DataPtr d = node->insData[iport].lock();
114                 pugi::xml_node port = input.append_child("port");
115
116                 port.append_attribute("id").set_value(iport);
117
118                 for (auto dim : d->getDims()) {
119                     port.append_child("dim").text().set(dim);
120                 }
121             }
122         }
123         if (!node->outData.empty()) {
124             pugi::xml_node input = layer.append_child("output");
125             for (size_t oport = 0; oport < node->outData.size(); oport++) {
126                 pugi::xml_node port = input.append_child("port");
127
128                 port.append_attribute("id").set_value(node->insData.size() + oport);
129
130                 for (const auto dim : node->outData[oport]->getDims()) {
131                     port.append_child("dim").text().set(dim);
132                 }
133             }
134         }
135         if (dumpWeights && !node->blobs.empty()) {
136             auto blobsNode = layer.append_child("blobs");
137             for (const auto &dataIt : node->blobs) {
138                 const char *dataPtr = dataIt.second->buffer().as<char*>();
139
140                 size_t dataSize = dataIt.second->byteSize();
141                 pugi::xml_node data = blobsNode.append_child(dataIt.first.c_str());
142                 data.append_attribute("offset").set_value(dataOffset);
143                 data.append_attribute("size").set_value(dataSize);
144
145                 dataOffset += dataSize;
146                 ofsBin.write(dataPtr, dataSize);
147                 if (!ofsBin.good()) {
148                     THROW_IE_EXCEPTION << "Error during '" << binPath << "' writing";
149                 }
150             }
151         }
152     }
153
154     if (dumpWeights) {
155         ofsBin.close();
156         if (!ofsBin.good()) {
157             THROW_IE_EXCEPTION << "Error during '" << binPath << "' closing";
158         }
159     }
160
161     pugi::xml_node edges = netXml.append_child("edges");
162
163     for (const auto &ord : ordered) {
164         const CNNLayer::Ptr node = ord;
165
166         if (!node->outData.empty()) {
167             auto itFrom = matching.find(node);
168             if (itFrom == matching.end()) {
169                 THROW_IE_EXCEPTION << "Internal error, cannot find " << node->name << " in matching container during serialization of IR";
170             }
171             for (size_t oport = 0; oport < node->outData.size(); oport++) {
172                 const DataPtr outData = node->outData[oport];
173                 for (const auto &inputTo : outData->inputTo) {
174                     auto itTo = matching.find(inputTo.second);
175                     if (itTo == matching.end()) {
176                         THROW_IE_EXCEPTION << "Broken edge form layer " << node->name << " to layer "  << inputTo.first<< "during serialization of IR";
177                     }
178
179                     int foundPort = -1;
180                     for (int iport = 0; iport < inputTo.second->insData.size(); iport++) {
181                         if (inputTo.second->insData[iport].lock() == outData) {
182                             foundPort = iport;
183                         }
184                     }
185                     if (foundPort == -1) {
186                         THROW_IE_EXCEPTION << "Broken edge from layer to parent, cannot find parent " << outData->name << " for layer " << inputTo.second->name
187                             << "\ninitial layer for edge output " << node->name;
188                     }
189                     pugi::xml_node edge = edges.append_child("edge");
190
191                     edge.append_attribute("from-layer").set_value(itFrom->second);
192                     edge.append_attribute("from-port").set_value(oport + node->insData.size());
193
194                     edge.append_attribute("to-layer").set_value(itTo->second);
195                     edge.append_attribute("to-port").set_value(foundPort);
196                 }
197             }
198         }
199     }
200
201     // no need to print this info in case of executable graph info serialization
202     if (!execGraphInfoSerialization) {
203         updatePreProcInfo(network, netXml);
204         updateStatisticsInfo(network, netXml);
205     }
206
207     if (!doc.save_file(xmlPath.c_str())) {
208         THROW_IE_EXCEPTION << "file '" << xmlPath << "' was not serialized";
209     }
210 }
211
212 void NetworkSerializer::updateStdLayerParams(const CNNLayer::Ptr &layer) {
213     auto layerPtr = layer.get();
214     auto &params = layer->params;
215
216     if (CaselessEq<std::string>()(layer->type, "power")) {
217         auto *lr = dynamic_cast<PowerLayer *>(layerPtr);
218
219         params["scale"] = std::to_string(lr->scale);
220         params["shift"] = std::to_string(lr->offset);
221         params["power"] = std::to_string(lr->power);
222     } else if (CaselessEq<std::string>()(layer->type, "convolution") ||
223                CaselessEq<std::string>()(layer->type, "deconvolution")) {
224         auto *lr = dynamic_cast<ConvolutionLayer *>(layerPtr);
225
226         params["kernel"] = arrayRevertToIRProperty(lr->_kernel);
227         params["pads_begin"] = arrayRevertToIRProperty(lr->_padding);
228         params["pads_end"] = arrayRevertToIRProperty(lr->_pads_end);
229         params["strides"] = arrayRevertToIRProperty(lr->_stride);
230         params["dilations"] = arrayRevertToIRProperty(lr->_dilation);
231         params["output"] = std::to_string(lr->_out_depth);
232         params["group"] = std::to_string(lr->_group);
233     } else if (CaselessEq<std::string>()(layer->type, "relu")) {
234         auto *lr = dynamic_cast<ReLULayer *>(layerPtr);
235         if (lr->negative_slope != 0.0f) {
236             params["negative_slope"] = std::to_string(lr->negative_slope);
237         }
238     } else if (CaselessEq<std::string>()(layer->type, "norm") ||
239                CaselessEq<std::string>()(layer->type, "lrn")) {
240         auto *lr = dynamic_cast<NormLayer *>(layerPtr);
241
242         params["alpha"] = std::to_string(lr->_alpha);
243         params["beta"] = std::to_string(lr->_beta);
244         params["local-size"] = std::to_string(lr->_size);
245         params["region"] = lr->_isAcrossMaps ? "across" : "same";
246     } else if (CaselessEq<std::string>()(layer->type, "pooling")) {
247         auto *lr = dynamic_cast<PoolingLayer *>(layerPtr);
248
249         params["kernel"] = arrayRevertToIRProperty(lr->_kernel);
250         params["pads_begin"] = arrayRevertToIRProperty(lr->_padding);
251         params["pads_end"] = arrayRevertToIRProperty(lr->_pads_end);
252         params["strides"] = arrayRevertToIRProperty(lr->_stride);
253
254         switch (lr->_type) {
255             case PoolingLayer::MAX:
256                 params["pool-method"] = "max";
257                 break;
258             case PoolingLayer::AVG:
259                 params["pool-method"] = "avg";
260                 break;
261
262             default:
263                 THROW_IE_EXCEPTION << "Found unsupported pooling method: " << lr->_type;
264         }
265     } else if (CaselessEq<std::string>()(layer->type, "split")) {
266         auto *lr = dynamic_cast<SplitLayer *>(layerPtr);
267         params["axis"] = std::to_string(lr->_axis);
268     } else if (CaselessEq<std::string>()(layer->type, "concat")) {
269         auto *lr = dynamic_cast<ConcatLayer *>(layerPtr);
270         params["axis"] = std::to_string(lr->_axis);
271     } else if (CaselessEq<std::string>()(layer->type, "FullyConnected") ||
272                CaselessEq<std::string>()(layer->type, "InnerProduct")) {
273         auto *lr = dynamic_cast<FullyConnectedLayer *>(layerPtr);
274         params["out-size"] = std::to_string(lr->_out_num);
275     } else if (CaselessEq<std::string>()(layer->type, "softmax")) {
276         auto *lr = dynamic_cast<SoftMaxLayer *>(layerPtr);
277         params["axis"] = std::to_string(lr->axis);
278     } else if (CaselessEq<std::string>()(layer->type, "reshape")) {
279         // need to add here support of flatten layer if it is created from API
280         auto *lr = dynamic_cast<ReshapeLayer *>(layerPtr);
281         params["dim"] = arrayToIRProperty(lr->shape);
282     } else if (CaselessEq<std::string>()(layer->type, "Eltwise")) {
283         auto *lr = dynamic_cast<EltwiseLayer *>(layerPtr);
284
285         std::string op;
286
287         switch (lr->_operation) {
288             case EltwiseLayer::Sum:
289                 op = "sum";
290                 break;
291             case EltwiseLayer::Prod:
292                 op = "prod";
293                 break;
294             case EltwiseLayer::Max:
295                 op = "max";
296                 break;
297             default:
298                 break;
299         }
300
301         params["operation"] = op;
302     } else if (CaselessEq<std::string>()(layer->type, "scaleshift")) {
303         auto *lr = dynamic_cast<ScaleShiftLayer *>(layerPtr);
304         params["broadcast"] = std::to_string(lr->_broadcast);
305     } else if (CaselessEq<std::string>()(layer->type, "crop")) {
306         auto *lr = dynamic_cast<CropLayer *>(layerPtr);
307         params["axis"] = arrayToIRProperty(lr->axis);
308         params["offset"] = arrayToIRProperty(lr->offset);
309         params["dim"] = arrayToIRProperty(lr->dim);
310     } else if (CaselessEq<std::string>()(layer->type, "tile")) {
311         auto *lr = dynamic_cast<TileLayer *>(layerPtr);
312         params["axis"] = std::to_string(lr->axis);
313         params["tiles"] = std::to_string(lr->tiles);
314     } else if (CaselessEq<std::string>()(layer->type, "prelu")) {
315         auto *lr = dynamic_cast<PReLULayer *>(layerPtr);
316         params["channel_shared"] = std::to_string(lr->_channel_shared);
317     } else if (CaselessEq<std::string>()(layer->type, "clamp")) {
318         auto *lr = dynamic_cast<ClampLayer *>(layerPtr);
319         params["min"] = std::to_string(lr->min_value);
320         params["max"] = std::to_string(lr->max_value);
321     } else if (CaselessEq<std::string>()(layer->type, "BatchNormalization")) {
322         auto *lr = dynamic_cast<BatchNormalizationLayer *>(layerPtr);
323         params["epsilon"] = std::to_string(lr->epsilon);
324     } else if (CaselessEq<std::string>()(layer->type, "grn")) {
325         auto *lr = dynamic_cast<GRNLayer *>(layerPtr);
326         params["bias"] = std::to_string(lr->bias);
327     } else if (CaselessEq<std::string>()(layer->type, "mvn")) {
328         auto *lr = dynamic_cast<MVNLayer *>(layerPtr);
329         params["across_channels"] = std::to_string(lr->across_channels);
330         params["normalize_variance"] = std::to_string(lr->normalize);
331     } else if (CaselessEq<std::string>()(layer->type, "rnn") ||
332                CaselessEq<std::string>()(layer->type, "TensorIterator") ||
333                CaselessEq<std::string>()(layer->type, "LSTMCell")) {
334         THROW_IE_EXCEPTION << "Not covered layers for writing to IR";
335     }
336
337     if (layer->params.find("quantization_level") != layer->params.end()) {
338         params["quantization_level"] = layer->params["quantization_level"];
339     }
340
341     // update of weightable layers
342     auto *pwlayer = dynamic_cast<WeightableLayer *>(layerPtr);
343     if (pwlayer) {
344         if (pwlayer->_weights) {
345             pwlayer->blobs["weights"] = pwlayer->_weights;
346         }
347         if (pwlayer->_biases) {
348             pwlayer->blobs["biases"] = pwlayer->_biases;
349         }
350     }
351 }
352
353 void NetworkSerializer::updatePreProcInfo(const InferenceEngine::ICNNNetwork& network, pugi::xml_node &netXml) {
354     InputsDataMap inputInfo;
355     network.getInputsInfo(inputInfo);
356
357     // Assume that you preprocess only one input
358     for (auto ii : inputInfo) {
359         const PreProcessInfo &pp = ii.second->getPreProcess();
360         size_t nInChannels = pp.getNumberOfChannels();
361         if (nInChannels) {
362             pugi::xml_node preproc = netXml.append_child("pre-process");
363
364             preproc.append_attribute("reference-layer-name").set_value(ii.first.c_str());
365             preproc.append_attribute("mean-precision").set_value(Precision(Precision::FP32).name());
366
367             for (size_t ch = 0; ch < nInChannels; ch++) {
368                 const PreProcessChannel::Ptr &preProcessChannel = pp[ch];
369                 auto channel = preproc.append_child("channel");
370                 channel.append_attribute("id").set_value(ch);
371
372                 auto mean = channel.append_child("mean");
373
374                 if (!preProcessChannel->meanData) {
375                     mean.append_attribute("value").set_value(preProcessChannel->meanValue);
376                 } else {
377                     THROW_IE_EXCEPTION << "Mean data is not supported yet for serialization of the model";
378                 }
379             }
380         }
381     }
382 }
383
384 void NetworkSerializer::updateStatisticsInfo(const InferenceEngine::ICNNNetwork& network, pugi::xml_node &netXml) {
385     // If statistics exists, add it to the file
386     ICNNNetworkStats *netNodesStats = nullptr;
387     auto stats = netXml.append_child("statistics");
388     network.getStats(&netNodesStats, nullptr);
389     const NetworkStatsMap statsmap = netNodesStats->getNodesStats();
390
391     auto joinCommas = [&](const std::vector<float> &v) -> std::string {
392         std::string res;
393
394         for (size_t i = 0; i < v.size(); ++i) {
395             res += std::to_string(v[i]);
396             if (i < v.size() - 1) {
397                 res += ", ";
398             }
399         }
400
401         return res;
402     };
403
404     for (const auto &itStats : statsmap) {
405         auto layer = stats.append_child("layer");
406
407         layer.append_child("name").text().set(itStats.first.c_str());
408
409         layer.append_child("min").text().set(joinCommas(itStats.second->_minOutputs).c_str());
410         layer.append_child("max").text().set(joinCommas(itStats.second->_maxOutputs).c_str());
411     }
412 }