Publishing 2019 R1 content
[platform/upstream/dldt.git] / inference-engine / src / cldnn_engine / cldnn_custom_layer.cpp
1 // Copyright (C) 2018-2019 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
3 //
4
5 #include "cldnn_custom_layer.h"
6 #include "xml_parse_utils.h"
7 #include <description_buffer.hpp>
8 #include <map>
9 #include <fstream>
10 #include <streambuf>
11 #include <climits>
12
13 #ifdef _WIN32
14 # include <windows.h>
15 #endif
16
17 #include "simple_math.h"
18
19 using namespace InferenceEngine;
20 using namespace XMLParseUtils;
21
22 #define CheckAndReturnError(cond, errorMsg) \
23     if (cond) { std::stringstream ss; ss << errorMsg; m_ErrorMessage = ss.str(); return; }
24 #define CheckNodeTypeAndReturnError(node, type) \
25     CheckAndReturnError((std::string(node.name()).compare(type)), "Wrong node! expected: " << #type << " found: " << node.name())
26 #define CheckStrAttrAndReturnError(node, attr, value) \
27     CheckAndReturnError(GetStrAttr(node, attr, "").compare(value), "Wrong attribute value! expected: " << value << " found: " << GetStrAttr(node, attr, ""))
28 #define CheckIntAttrAndReturnError(node, attr, value) \
29     CheckAndReturnError(GetIntAttr(node, attr, -1) != (value), "Wrong attribute value! expected: " << value << " found: " << GetIntAttr(node, attr, -1))
30
31 namespace CLDNNPlugin {
32
33 void CLDNNCustomLayer::LoadSingleLayer(const pugi::xml_node & node) {
34     // Root checks
35     CheckNodeTypeAndReturnError(node, "CustomLayer");
36     CheckStrAttrAndReturnError(node, "type", "SimpleGPU");
37     CheckIntAttrAndReturnError(node, "version", 1);
38     m_layerName = GetStrAttr(node, "name", "");
39     CheckAndReturnError(m_layerName.length() == 0, "Missing Layer name in CustomLayer");
40
41     // Process child nodes
42     ProcessKernelNode(node.child("Kernel"));
43     ProcessBuffersNode(node.child("Buffers"));
44     ProcessCompilerOptionsNode(node.child("CompilerOptions"));
45     ProcessWorkSizesNode(node.child("WorkSizes"));
46 }
47
48 void CLDNNCustomLayer::ProcessKernelNode(const pugi::xml_node & node) {
49     CheckNodeTypeAndReturnError(node, "Kernel");
50     CheckAndReturnError(m_kernelSource.length() > 0, "Multiple definition of Kernel");
51     m_kernelEntry = GetStrAttr(node, "entry", "");
52     CheckAndReturnError(m_kernelEntry.length() == 0, "No Kernel entry in layer: " << GetStrAttr(node.parent(), "name"));
53
54     // Handle Source nodes
55     for (auto sourceNode = node.child("Source"); !sourceNode.empty(); sourceNode = sourceNode.next_sibling("Source")) {
56         // open file
57         std::string filename = m_configDir + "/" + GetStrAttr(sourceNode, "filename", "");
58         std::ifstream inputFile(filename);
59         CheckAndReturnError(!inputFile.is_open(), "Couldn't open kernel file: " << filename);
60
61         // read to string
62         std::string fileContent;
63         inputFile.seekg(0, std::ios::end);
64         fileContent.reserve(inputFile.tellg());
65         inputFile.seekg(0, std::ios::beg);
66
67         fileContent.assign((std::istreambuf_iterator<char>(inputFile)),
68             std::istreambuf_iterator<char>());
69
70         // append to source string
71         m_kernelSource.append("\n// Custom Layer Kernel " + filename + "\n\n");
72         m_kernelSource.append(fileContent);
73     }
74
75     // Handle Define nodes
76     for (auto defineNode = node.child("Define"); !defineNode.empty(); defineNode = defineNode.next_sibling("Define")) {
77         KernelDefine kd;
78         kd.name = GetStrAttr(defineNode, "name", "");
79         CheckAndReturnError((kd.name.length() == 0), "Missing name for define node");
80         kd.param = GetStrAttr(defineNode, "param", "");
81         kd.default_value = GetStrAttr(defineNode, "default", "");
82         std::string type = GetStrAttr(defineNode, "type", "");
83         if (type.compare("int[]") == 0 || type.compare("float[]") == 0) {
84             kd.prefix = "(" + type + ") {";
85             kd.postfix = "}";
86         }
87         m_defines.push_back(kd);
88     }
89 }
90
91 void CLDNNCustomLayer::ProcessBuffersNode(const pugi::xml_node & node) {
92     CheckNodeTypeAndReturnError(node, "Buffers");
93     for (auto tensorNode = node.child("Tensor"); !tensorNode.empty(); tensorNode = tensorNode.next_sibling("Tensor")) {
94         KerenlParam kp;
95         kp.format = FormatFromString(GetStrAttr(tensorNode, "format", "BFYX"));
96         CheckAndReturnError(kp.format == cldnn::format::format_num, "Tensor node has an invalid format: " << GetStrAttr(tensorNode, "format"));
97         kp.paramIndex = GetIntAttr(tensorNode, "arg-index", -1);
98         CheckAndReturnError(kp.paramIndex == -1, "Tensor node has no arg-index");
99         kp.portIndex = GetIntAttr(tensorNode, "port-index", -1);
100         CheckAndReturnError(kp.portIndex == -1, "Tensor node has no port-index");
101         std::string typeStr = GetStrAttr(tensorNode, "type");
102         if (typeStr.compare("input") == 0) {
103             kp.type = ParamType::Input;
104         } else if (typeStr.compare("output") == 0) {
105             kp.type = ParamType::Output;
106         } else {
107             CheckAndReturnError(true, "Tensor node has an invalid type: " << typeStr);
108         }
109         m_kernelParams.push_back(kp);
110     }
111     for (auto dataNode = node.child("Data"); !dataNode.empty(); dataNode = dataNode.next_sibling("Data")) {
112         KerenlParam kp;
113         kp.type = ParamType::Data;
114         kp.paramIndex = GetIntAttr(dataNode, "arg-index", -1);
115         CheckAndReturnError(kp.paramIndex == -1, "Data node has no arg-index");
116         kp.blobName = GetStrAttr(dataNode, "name", "");
117         CheckAndReturnError(kp.blobName.empty(), "Data node has no name");
118         m_kernelParams.push_back(kp);
119     }
120 }
121
122 void CLDNNCustomLayer::ProcessCompilerOptionsNode(const pugi::xml_node & node) {
123     if (node.empty()) {
124         return;  // Optional node doesn't exist
125     }
126     CheckNodeTypeAndReturnError(node, "CompilerOptions");
127     CheckAndReturnError(m_compilerOptions.length() > 0, "Multiple definition of CompilerOptions");
128     m_compilerOptions = GetStrAttr(node, "options", "");
129 }
130
131 void CLDNNCustomLayer::ProcessWorkSizesNode(const pugi::xml_node & node) {
132     if (node.empty()) {
133         return;  // Optional node doesn't exist
134     }
135     CheckNodeTypeAndReturnError(node, "WorkSizes");
136
137     m_wgDimInputIdx = -1;
138     std::string dim_src_string = node.attribute("dim").as_string("");
139     if (!dim_src_string.empty() && "output" != dim_src_string) {
140         // try to locate index separator
141         auto pos = dim_src_string.find_first_of(',');
142         auto flag = dim_src_string.substr(0, pos);
143         CheckAndReturnError(("input" != flag), "Invalid WG dim source: " << flag);
144
145         int input_idx = 0;
146         if (pos != std::string::npos) {
147             // user explicitly set input index in config
148             auto input_idx_string = dim_src_string.substr(pos + 1, std::string::npos);
149             input_idx = std::stoi(input_idx_string);
150         }
151         CheckAndReturnError((input_idx < 0), "Invalid input tensor index: " << input_idx);
152         m_wgDimInputIdx = input_idx;
153     }
154
155     std::string gws = node.attribute("global").as_string("");
156     while (!gws.empty()) {
157         auto pos = gws.find_first_of(',');
158         auto rule = gws.substr(0, pos);
159         CheckAndReturnError(!IsLegalSizeRule(rule), "Invalid WorkSize: " << rule);
160         m_globalSizeRules.push_back(rule);
161         if (pos == std::string::npos) {
162             gws.clear();
163         } else {
164             gws = gws.substr(pos + 1, std::string::npos);
165         }
166     }
167
168     std::string lws = node.attribute("local").as_string("");
169     while (!lws.empty()) {
170         auto pos = lws.find_first_of(',');
171         auto rule = lws.substr(0, pos);
172         CheckAndReturnError(!IsLegalSizeRule(rule), "Invalid WorkSize: " << rule);
173         m_localSizeRules.push_back(rule);
174         if (pos == std::string::npos) {
175             lws.clear();
176         } else {
177             lws = lws.substr(pos + 1, std::string::npos);
178         }
179     }
180 }
181
182 bool CLDNNCustomLayer::IsLegalSizeRule(const std::string & rule) {
183     SimpleMathExpression expr;
184     expr.SetVariables({
185         { 'b', 1 }, { 'B', 1 },
186         { 'f', 1 }, { 'F', 1 },
187         { 'y', 1 }, { 'Y', 1 },
188         { 'x', 1 }, { 'X', 1 },
189     });
190     if (!expr.SetExpression(rule)) {
191         return false;
192     }
193
194     try {
195         expr.Evaluate();
196     } catch (...) {
197         return false;
198     }
199     return true;
200 }
201
202 cldnn::format CLDNNCustomLayer::FormatFromString(const std::string & str) {
203     static const std::map<std::string, cldnn::format> FormatNameToType = {
204         { "BFYX" , cldnn::format::bfyx },
205         { "bfyx" , cldnn::format::bfyx },
206
207         { "BYXF" , cldnn::format::byxf },
208         { "byxf" , cldnn::format::byxf },
209
210         { "FYXB" , cldnn::format::fyxb },
211         { "fyxb" , cldnn::format::fyxb },
212
213         { "YXFB" , cldnn::format::yxfb },
214         { "yxfb" , cldnn::format::yxfb },
215
216         { "ANY" , cldnn::format::any },
217         { "any" , cldnn::format::any },
218     };
219     auto it = FormatNameToType.find(str);
220     if (it != FormatNameToType.end())
221         return it->second;
222     else
223         return cldnn::format::format_num;
224 }
225
226 void CLDNNCustomLayer::LoadFromFile(const std::string configFile, CLDNNCustomLayerMap& customLayers, bool can_be_missed) {
227     pugi::xml_document xmlDoc;
228     pugi::xml_parse_result res = xmlDoc.load_file(configFile.c_str());
229     if (res.status != pugi::status_ok) {
230         if (can_be_missed) {
231             // config file might not exist - like global config, for example
232             return;
233         } else {
234             THROW_IE_EXCEPTION << "Error loading custom layer configuration file: " << configFile << ", " << res.description()
235                 << " at offset " << res.offset;
236         }
237     }
238
239 #ifdef _WIN32
240     char path[MAX_PATH];
241     char* abs_path_ptr = _fullpath(path, configFile.c_str(), MAX_PATH);
242 #elif __linux__
243     char path[PATH_MAX];
244     char* abs_path_ptr = realpath(configFile.c_str(), path);
245 #endif
246     if (abs_path_ptr == nullptr) {
247         THROW_IE_EXCEPTION << "Error loading custom layer configuration file: " << configFile << ", "
248                            << "Can't get canonicalized absolute pathname.";
249     }
250
251     std::string abs_file_name(path);
252     // try extracting directory from config path
253     std::string dir_path;
254     std::size_t dir_split_pos = abs_file_name.find_last_of("/\\");
255     std::size_t colon_pos = abs_file_name.find_first_of(":");
256     std::size_t first_slash_pos = abs_file_name.find_first_of("/");
257
258     if (dir_split_pos != std::string::npos &&
259        (colon_pos != std::string::npos || first_slash_pos == 0)) {
260         // path is absolute
261         dir_path = abs_file_name.substr(0, dir_split_pos);
262     } else {
263         THROW_IE_EXCEPTION << "Error loading custom layer configuration file: " << configFile << ", "
264                            << "Path is not valid";
265     }
266
267     for (auto r = xmlDoc.document_element(); r; r = r.next_sibling()) {
268         CLDNNCustomLayerPtr layer = std::make_shared<CLDNNCustomLayer>(CLDNNCustomLayer(dir_path));
269         layer->LoadSingleLayer(r);
270         if (layer->Error()) {
271             customLayers.clear();
272             THROW_IE_EXCEPTION << layer->m_ErrorMessage;
273         } else {
274             customLayers[layer->Name()] = layer;
275         }
276     }
277 }
278
279 };  // namespace CLDNNPlugin