1 // Copyright (C) 2018-2019 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
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"
16 using namespace InferenceEngine;
17 using namespace details;
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) ? "," : "");
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) ? "," : "");
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);
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";
58 bool dumpWeights = !execGraphInfoSerialization & !binPath.empty();
61 ofsBin.open(binPath, std::ofstream::out | std::ofstream::binary);
63 THROW_IE_EXCEPTION << "File '" << binPath << "' is not opened as out file stream";
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());
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());
77 pugi::xml_node layers = netXml.append_child("layers");
79 std::map<CNNLayer::Ptr, size_t> matching;
80 for (size_t i = 0; i < ordered.size(); i++) {
81 matching[ordered[i]] = i;
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];
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);
96 if (!execGraphInfoSerialization) {
97 updateStdLayerParams(node);
100 const auto ¶ms = node->params;
101 if (!params.empty()) {
102 pugi::xml_node data = layer.append_child(dataName.c_str());
104 for (const auto &it : params) {
105 data.append_attribute(it.first.c_str()).set_value(it.second.c_str());
109 if (!node->insData.empty()) {
110 pugi::xml_node input = layer.append_child("input");
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");
116 port.append_attribute("id").set_value(iport);
118 for (auto dim : d->getDims()) {
119 port.append_child("dim").text().set(dim);
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");
128 port.append_attribute("id").set_value(node->insData.size() + oport);
130 for (const auto dim : node->outData[oport]->getDims()) {
131 port.append_child("dim").text().set(dim);
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*>();
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);
145 dataOffset += dataSize;
146 ofsBin.write(dataPtr, dataSize);
147 if (!ofsBin.good()) {
148 THROW_IE_EXCEPTION << "Error during '" << binPath << "' writing";
156 if (!ofsBin.good()) {
157 THROW_IE_EXCEPTION << "Error during '" << binPath << "' closing";
161 pugi::xml_node edges = netXml.append_child("edges");
163 for (const auto &ord : ordered) {
164 const CNNLayer::Ptr node = ord;
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";
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";
180 for (int iport = 0; iport < inputTo.second->insData.size(); iport++) {
181 if (inputTo.second->insData[iport].lock() == outData) {
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;
189 pugi::xml_node edge = edges.append_child("edge");
191 edge.append_attribute("from-layer").set_value(itFrom->second);
192 edge.append_attribute("from-port").set_value(oport + node->insData.size());
194 edge.append_attribute("to-layer").set_value(itTo->second);
195 edge.append_attribute("to-port").set_value(foundPort);
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);
207 if (!doc.save_file(xmlPath.c_str())) {
208 THROW_IE_EXCEPTION << "file '" << xmlPath << "' was not serialized";
212 void NetworkSerializer::updateStdLayerParams(const CNNLayer::Ptr &layer) {
213 auto layerPtr = layer.get();
214 auto ¶ms = layer->params;
216 if (CaselessEq<std::string>()(layer->type, "power")) {
217 auto *lr = dynamic_cast<PowerLayer *>(layerPtr);
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);
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);
238 } else if (CaselessEq<std::string>()(layer->type, "norm") ||
239 CaselessEq<std::string>()(layer->type, "lrn")) {
240 auto *lr = dynamic_cast<NormLayer *>(layerPtr);
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);
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);
255 case PoolingLayer::MAX:
256 params["pool-method"] = "max";
258 case PoolingLayer::AVG:
259 params["pool-method"] = "avg";
263 THROW_IE_EXCEPTION << "Found unsupported pooling method: " << lr->_type;
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);
287 switch (lr->_operation) {
288 case EltwiseLayer::Sum:
291 case EltwiseLayer::Prod:
294 case EltwiseLayer::Max:
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";
337 if (layer->params.find("quantization_level") != layer->params.end()) {
338 params["quantization_level"] = layer->params["quantization_level"];
341 // update of weightable layers
342 auto *pwlayer = dynamic_cast<WeightableLayer *>(layerPtr);
344 if (pwlayer->_weights) {
345 pwlayer->blobs["weights"] = pwlayer->_weights;
347 if (pwlayer->_biases) {
348 pwlayer->blobs["biases"] = pwlayer->_biases;
353 void NetworkSerializer::updatePreProcInfo(const InferenceEngine::ICNNNetwork& network, pugi::xml_node &netXml) {
354 InputsDataMap inputInfo;
355 network.getInputsInfo(inputInfo);
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();
362 pugi::xml_node preproc = netXml.append_child("pre-process");
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());
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);
372 auto mean = channel.append_child("mean");
374 if (!preProcessChannel->meanData) {
375 mean.append_attribute("value").set_value(preProcessChannel->meanValue);
377 THROW_IE_EXCEPTION << "Mean data is not supported yet for serialization of the model";
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();
391 auto joinCommas = [&](const std::vector<float> &v) -> std::string {
394 for (size_t i = 0; i < v.size(); ++i) {
395 res += std::to_string(v[i]);
396 if (i < v.size() - 1) {
404 for (const auto &itStats : statsmap) {
405 auto layer = stats.append_child("layer");
407 layer.append_child("name").text().set(itStats.first.c_str());
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());