Publishing 2019 R1 content
[platform/upstream/dldt.git] / inference-engine / thirdparty / clDNN / api / CPP / layout.hpp
1 /*
2 // Copyright (c) 2016-2018 Intel Corporation
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 ///////////////////////////////////////////////////////////////////////////////////////////////////
18 #pragma once
19 #include "tensor.hpp"
20 #include <cmath>
21 #include <cstdlib>
22
23 namespace cldnn
24 {
25 /// @addtogroup cpp_api C++ API
26 /// @{
27
28 /// @addtogroup cpp_memory Memory description and management
29 /// @{
30
31 /// @brief Possible data types could be stored in memory.
32 enum class data_types : size_t
33 {
34     i8 = cldnn_i8,/// Not supported in current HW
35     u8 = cldnn_u8,/// 
36     i32 = cldnn_i32,
37     i64 = cldnn_i64,
38     f16 = cldnn_f16,
39     f32 = cldnn_f32,
40 };
41
42 class optional_data_type
43 {
44     // Must be the same as the undrelying type of `data_types`.
45     using storage_type = size_t;
46
47     // Implicitly assumes that this value is not used in the `data_types`.
48     static constexpr auto non_specified_type =
49         std::numeric_limits<storage_type>::max();
50
51 public:
52     optional_data_type()
53         : storage(non_specified_type)
54     {}
55
56     optional_data_type(data_types type)
57         : storage(static_cast<storage_type>(type))
58     {}
59
60     operator bool() const { return storage != non_specified_type; }
61
62     // Similarly to std::optional does *not* verify that the object has the type
63     // set. Unlike it, though, returns the value instead of pointer/reference.
64     data_types operator*() const { return static_cast<data_types>(storage); }
65
66     optional_data_type& operator=(const data_types new_type)
67     {
68         storage = static_cast<storage_type>(new_type);
69         return *this;
70     }
71
72 private:
73     storage_type storage;
74 };
75
76 /// Converts C++ type to @ref data_types .
77 template <typename T> struct type_to_data_type;
78 #ifndef DOXYGEN_SHOULD_SKIP_THIS
79 template<> struct type_to_data_type<int8_t>  { static const data_types value = data_types::i8; };
80 template<> struct type_to_data_type<uint8_t> { static const data_types value = data_types::u8; };
81 template<> struct type_to_data_type<int32_t> { static const data_types value = data_types::i32; };
82 template<> struct type_to_data_type<int64_t> { static const data_types value = data_types::i64; };
83 template<> struct type_to_data_type<half_t>  { static const data_types value = data_types::f16; };
84 template<> struct type_to_data_type<float>   { static const data_types value = data_types::f32; };
85 #endif
86
87 /// Converts @ref data_types to C++ type.
88 template<data_types Data_Type> struct data_type_to_type;
89 #ifndef DOXYGEN_SHOULD_SKIP_THIS
90 template<> struct data_type_to_type<data_types::i8>  { typedef int8_t type; };
91 template<> struct data_type_to_type<data_types::i32> { typedef int32_t type; };
92 template<> struct data_type_to_type<data_types::i64> { typedef int64_t type; };
93 template<> struct data_type_to_type<data_types::f16> { typedef half_t type; };
94 template<> struct data_type_to_type<data_types::f32> { typedef float type; };
95 #endif
96
97
98 /// Helper class to identify key properties for data_types.
99 struct data_type_traits
100 {
101     static size_t size_of(data_types data_type)
102     {
103         return (static_cast<uint32_t>(data_type) & ~(CLDNN_FLOAT_TYPE_MASK | CLDNN_UINT_TYPE_MASK));
104     }
105
106     static bool is_floating_point(data_types data_type)
107     {
108         return (static_cast<uint32_t>(data_type) & CLDNN_FLOAT_TYPE_MASK) != 0;
109     }
110
111     static size_t align_of(data_types data_type)
112     {
113         switch (data_type)
114         {
115         case data_types::i8:
116             return alignof(data_type_to_type<data_types::i8>::type);
117         case data_types::i32:
118             return alignof(data_type_to_type<data_types::i32>::type);
119         case data_types::i64:
120             return alignof(data_type_to_type<data_types::i64>::type);
121         case data_types::f16:
122             return alignof(data_type_to_type<data_types::f16>::type);
123         case data_types::f32:
124             return alignof(data_type_to_type<data_types::f32>::type);
125         default: return size_t(1);
126         }
127     }
128
129     static std::string name(data_types data_type)
130     {
131         switch (data_type)
132         {
133         case data_types::i8:
134             return "i8";
135         case data_types::u8:
136             return "u8";
137         case data_types::i32:
138             return "i32";
139         case data_types::i64:
140             return "i64";
141         case data_types::f16:
142             return "f16";
143         case data_types::f32:
144             return "f32";
145         default:
146             assert(0);
147             return std::string("invalid data type: " + std::to_string((int)data_type));
148         }
149     }
150 };
151
152 /// Helper function to check if C++ type matches @p data_type.
153 template <typename T>
154 bool data_type_match(data_types data_type)
155 {
156     return data_type == type_to_data_type<T>::value;
157 }
158
159 /// Helper function to get both data_types and format::type in a single, unique value. Useable in 'case' statement.
160 constexpr auto fuse(data_types dt, cldnn::format::type fmt) -> decltype(static_cast<std::underlying_type<data_types>::type>(dt) | static_cast<std::underlying_type<format::type>::type>(fmt))
161 {
162     using dt_type = std::underlying_type<data_types>::type;
163     using fmt_type = std::underlying_type<cldnn::format::type>::type;
164     using fmt_narrow_type = int16_t;
165
166     return static_cast<fmt_type>(fmt) <= std::numeric_limits<fmt_narrow_type>::max() &&
167         static_cast<dt_type>(dt) <= (std::numeric_limits<dt_type>::max() >> (sizeof(fmt_narrow_type) * 8))
168         ? (static_cast<dt_type>(dt) << (sizeof(fmt_narrow_type) * 8)) |
169         (static_cast<fmt_type>(fmt) >= 0 ? static_cast<fmt_narrow_type>(fmt) : static_cast<fmt_narrow_type>(-1))
170         : throw std::invalid_argument("data_type and/or format values are too big to be fused into single value");
171 }
172
173
174 /// @brief Represents data padding information.
175 struct padding
176 {
177     /// @brief Filling value for padding area.
178     float filling_value() const { return _filling_value; }
179
180     /// @brief Gets lower padding sizes. For spatials, it means size of left (X) and top (Y) padding.
181     /// @return Tensor with padding for top/left/lower bounds of data.
182     tensor lower_size() const { return _lower_size; }
183
184     /// @brief Gets upper padding sizes. For spatials, it means size of right (X) and bottom (Y) padding.
185     /// @return Tensor with padding for bottom/right/upper bounds of data.
186     tensor upper_size() const { return _upper_size; }
187
188     /// @brief 
189     /// @param lower_sizes Top-left padding sizes. See @ref tensor::tensor(const std::vector<value_type>&, value_type) for details.
190     /// @param upper_sizes Bottom-right padding sizes. See @ref tensor::tensor(const std::vector<value_type>&, value_type) for details.
191     /// @param filling_value Filling value for padding area.
192     padding(const std::vector<tensor::value_type>& lower_sizes, const std::vector<tensor::value_type>& upper_sizes, float filling_value = 0.0f)
193         : _lower_size(to_abs(lower_sizes), 0), _upper_size(to_abs(upper_sizes), 0), _filling_value(filling_value)
194     {}
195
196     /// @brief Constrcuts symmetric padding.
197     /// @param sizes Top-left and bottom-right padding sizes. See @ref tensor::tensor(const std::vector<value_type>&, value_type) for details.
198     /// @param filling_value Filling value for padding area.
199     padding(const std::vector<tensor::value_type>& sizes, float filling_value = 0.0f)
200         : padding(sizes, sizes, filling_value)
201     {}
202
203     /// @brief Constructs "zero-sized" padding.
204     padding() : padding({ 0,0,0,0 }, 0) {}
205
206     /// @brief Copy construction.
207     padding(const cldnn_padding& other)
208         : _lower_size(other.lower_size), _upper_size(other.upper_size), _filling_value(other.filling_value)
209     {}
210
211     /// @brief Implicit conversion to C API @ref cldnn_padding.
212     operator cldnn_padding() const
213     {
214         return{ static_cast<cldnn_tensor>(_lower_size),
215                  static_cast<cldnn_tensor>(_upper_size),
216                  _filling_value };
217     }
218
219     /// @brief Returns true if padding size is not zero.
220     explicit operator bool() const
221     {
222         return std::any_of(_lower_size.raw.begin(), _lower_size.raw.end(), [](const tensor::value_type& el) { return el != 0; }) ||
223             std::any_of(_upper_size.raw.begin(), _upper_size.raw.end(), [](const tensor::value_type& el) { return el != 0; });
224     }
225
226     friend bool operator ==(const padding& lhs, const padding& rhs)
227     {
228         return lhs._lower_size == rhs._lower_size
229             && lhs._upper_size == rhs._upper_size
230             && lhs._filling_value == rhs._filling_value;
231     }
232
233     friend bool operator !=(const padding& lhs, const padding& rhs)
234     {
235         return !(lhs == rhs);
236     }
237
238     friend bool operator <(const padding& lhs, const padding& rhs)
239     {
240         if (lhs._filling_value != rhs._filling_value)
241             return (lhs._filling_value < rhs._filling_value);
242         if (lhs._lower_size != rhs._lower_size)
243             return (lhs._lower_size < rhs._lower_size);
244         return (lhs._upper_size < rhs._upper_size);
245     }
246
247     static padding max(padding const& lhs, padding const& rhs, float filling_value = 0.0f)
248     {
249         auto lower = tensor::max(lhs.lower_size(), rhs.lower_size());
250         auto upper = tensor::max(lhs.upper_size(), rhs.upper_size());
251         return padding{ lower.sizes(), upper.sizes(), filling_value };
252     }
253
254 private:
255     tensor _lower_size;    ///< Lower padding sizes. For spatials, it means size of left (X) and top (Y) padding.
256     tensor _upper_size;    ///< Upper padding sizes. For spatials, it means size of right (X) and bottom (Y) padding.
257     // TODO: Add support for non-zero filling value (if necessary) or remove variable (if not necessary).
258     float  _filling_value; ///< Filling value for an element of padding. If data type of elements is different than float it is converted
259                            ///< to it using round-towards-nearest-even (for floating-point data types) or round-towards-zero (for integral
260                            ///< data types).
261
262     static std::vector<tensor::value_type> to_abs(const std::vector<tensor::value_type>& sizes)
263     {
264         std::vector<tensor::value_type> result;
265         result.reserve(sizes.size());
266         std::transform(sizes.cbegin(), sizes.cend(), std::back_inserter(result), [](const tensor::value_type& el) { return abs(el); });
267         return result; // NRVO
268     }
269 };
270
271 /// @brief Describes memory layout.
272 /// @details Contains information about data stored in @ref memory.
273 struct layout
274 {
275     /// Constructs layout based on @p data_type and @p size information described by @ref tensor
276     layout(data_types data_type, cldnn::format fmt, tensor size, padding apadding = padding())
277         : data_type(data_type)
278         , format(fmt)
279         , size(size)
280         , data_padding(apadding)
281     {}
282
283     /// Construct C++ layout based on C API @p cldnn_layout
284     layout(const cldnn_layout& other)
285         : data_type(static_cast<data_types>(other.data_type))
286         , format(static_cast<cldnn::format::type>(other.format))
287         , size(other.size)
288         , data_padding(other.padding)
289     {}
290
291     /// Convert to C API @p cldnn_layout
292     operator cldnn_layout() const
293     {
294         return{ static_cast<decltype(cldnn_layout::data_type)>(data_type), static_cast<decltype(cldnn_layout::format)>(format), size, data_padding };
295     }
296
297     layout(const layout& other) = default;
298
299     layout& operator=(const layout& other)
300     {
301         if (this == &other)
302             return *this;
303         data_type = other.data_type;
304         format = other.format;
305         size = other.size;
306         data_padding = other.data_padding;
307         return *this;
308     }
309
310     friend bool operator==(const layout& lhs, const layout& rhs)
311     {
312         return lhs.data_type == rhs.data_type
313             && lhs.format == rhs.format
314             && lhs.size == rhs.size
315             && lhs.data_padding == rhs.data_padding;
316     }
317
318     friend bool operator!=(const layout& lhs, const layout& rhs)
319     {
320         return !(lhs == rhs);
321     }
322
323     friend bool operator<(const layout& lhs, const layout& rhs)
324     {
325         if (lhs.data_type != rhs.data_type)
326             return (lhs.data_type < rhs.data_type);
327         if (lhs.format != rhs.format)
328             return (lhs.format < rhs.format);
329         if (lhs.size < rhs.size)
330             return (lhs.size < rhs.size);
331         return (lhs.data_padding < rhs.data_padding);
332     }
333
334     /// Number of elements to be stored in this memory layout
335     size_t count() const { return size.count(); }
336
337     /// Layout size with padding included
338     tensor get_buffer_size() const
339     {
340         return size.add(data_padding.lower_size()).add(data_padding.upper_size());
341     }
342
343     tensor get_pitches() const
344     {
345         auto sizes = get_buffer_size().sizes(format);
346         if (format == format::byxf_af32)
347         {
348             sizes[3] = align_to(sizes[3], 32);
349         }
350
351         if (format == format::byx8_f4)
352         {
353             sizes[3] = align_to(sizes[3], 4);
354             sizes[2] = align_to(sizes[2], 8);
355         }
356         std::vector<tensor::value_type> pitches(sizes.size(), tensor::value_type(1));
357         std::partial_sum(sizes.rbegin(), sizes.rend() - 1, pitches.rbegin() + 1, std::multiplies<tensor::value_type>());
358         return{ format, pitches };
359     }
360
361     // @brief Calculates position within buffer of the data element pointed by the provided tensor.
362     // element == { 0,0,0,0 } means first no-padding (i.e. data) element
363     size_t get_linear_offset(tensor element = { 0,0,0,0 }) const
364     {
365         auto pitches = get_pitches();
366         auto l_padd = data_padding.lower_size();
367         auto u_padd = data_padding.upper_size();
368
369         if ((element.batch[0] < 0 && -element.batch[0] > l_padd.batch[0]) ||
370             (element.feature[0] < 0 && -element.feature[0] > l_padd.feature[0]) ||
371             (element.spatial[0] < 0 && -element.spatial[0] > l_padd.spatial[0]) ||
372             (element.spatial[1] < 0 && -element.spatial[1] > l_padd.spatial[1]) ||
373             (element.batch[0] >= size.batch[0] + u_padd.batch[0]) ||
374             (element.feature[0] >= size.feature[0] + u_padd.feature[0]) ||
375             (element.spatial[0] >= size.spatial[0] + u_padd.spatial[0]) ||
376             (element.spatial[1] >= size.spatial[1] + u_padd.spatial[1]))
377             throw std::invalid_argument("Requested to calculate linear offset for an element which lies outside of the buffer range.");
378
379         size_t linear_offset =
380             static_cast<size_t>(element.batch[0] + l_padd.batch[0]) * static_cast<size_t>(pitches.batch[0]) +
381             static_cast<size_t>(element.feature[0] + l_padd.feature[0]) * static_cast<size_t>(pitches.feature[0]) +
382             static_cast<size_t>(element.spatial[0] + l_padd.spatial[0]) * static_cast<size_t>(pitches.spatial[0]) +
383             static_cast<size_t>(element.spatial[1] + l_padd.spatial[1]) * static_cast<size_t>(pitches.spatial[1]);
384
385         return linear_offset;
386     }
387
388     /// @brief Get aligned linear size calculated as multiplication of all elements. 
389     size_t get_linear_size() const
390     {
391         auto sizes = get_buffer_size().sizes();
392         if (this->format == cldnn::format::os_iyx_osv16 && !is_aligned_to(sizes[0], 16))
393         {
394             sizes[0] = align_to(sizes[0], 16);
395         }
396         else if (this->format == cldnn::format::os_iyx_osv32 && !is_aligned_to(sizes[0], 32))
397         {
398             sizes[0] = align_to(sizes[0], 32);
399         }
400         else if (this->format == cldnn::format::os_iyx_osv64 && !is_aligned_to(sizes[0], 64))
401         {
402             sizes[0] = align_to(sizes[0], 64);
403         }
404         else if (this->format == cldnn::format::bs_xs_xsv8_bsv8 && !(is_aligned_to(sizes[0], 8) && is_aligned_to(sizes[2], 8)))
405         {
406             sizes[0] = align_to(sizes[0], 8);
407             sizes[2] = align_to(sizes[2], 8);
408         }
409         else if (this->format == cldnn::format::bs_xs_xsv8_bsv16 && !(is_aligned_to(sizes[0], 16) && is_aligned_to(sizes[2], 8)))
410         {
411             sizes[0] = align_to(sizes[0], 16);
412             sizes[2] = align_to(sizes[2], 8);
413         }
414         else if (this->format == cldnn::format::bs_x_bsv16 && !is_aligned_to(sizes[0], 16))
415         {
416             sizes[0] = align_to(sizes[0], 16);
417         }
418         else if (this->format == cldnn::format::bf8_xy16 && !(is_aligned_to(sizes[1], 8) && is_aligned_to(sizes[2] * sizes[3], 16)))
419         {
420             sizes[1] = align_to(sizes[1], 8);
421             sizes[3] = align_to(sizes[2]*sizes[3], 16);
422             sizes[2] = 1;
423         }
424         else if (this->format == cldnn::format::byxf_af32 && !(is_aligned_to(sizes[1], 32)))
425         {
426             sizes[1] = align_to(sizes[1], 32);
427         }
428         else if (this->format == cldnn::format::byx8_f4 && (!is_aligned_to(sizes[1], 4) || !is_aligned_to(sizes[2], 8)))
429         {
430             // for this case we want to make sure, that with padding we're aligned to 8 in x
431             auto lp = data_padding.lower_size().spatial[0];
432             auto up = data_padding.upper_size().spatial[0];
433             sizes[1] = align_to(sizes[1], 4);
434             sizes[2] = align_to(lp + up + sizes[2], 8);
435             sizes[2] -= lp + up;
436         }
437         else if (this->format == cldnn::format::fs_bs_yx_bsv4_fsv32 && (!(is_aligned_to(sizes[1], 32)) || !(is_aligned_to(sizes[0], 4)) ) )
438         {
439             sizes[1] = align_to(sizes[1], 32);
440             sizes[0] = align_to(sizes[0], 4);
441         }
442         else if (this->format == cldnn::format::b_fs_yx_fsv4 && !(is_aligned_to(sizes[1], 4)))
443         {
444             sizes[1] = align_to(sizes[1], 4);
445         }
446         else if (this->format == cldnn::format::os_is_yx_isa8_osv8_isv4 && !(is_aligned_to(sizes[0], 8)) && !(is_aligned_to(sizes[1], 32)))
447         {
448             sizes[0] = align_to(sizes[0], 8);
449             sizes[1] = align_to(sizes[1], 32);
450         }
451         else if (this->format == cldnn::format::os_is_yx_isa8_osv8_isv4_swizzled_by_4 && !(is_aligned_to(sizes[0], 32)) && !(is_aligned_to(sizes[1], 32)))
452         {
453             sizes[0] = align_to(sizes[0], 32);
454             sizes[1] = align_to(sizes[1], 32);
455         }
456         else if (this->format == cldnn::format::is_o_yx_isv32 && !(is_aligned_to(sizes[1], 32)))
457         {
458             sizes[1] = align_to(sizes[1], 32);
459         }
460         else if (this->format == cldnn::format::is_o32_yx_isv32_swizzled_by_4 && (!is_aligned_to(sizes[1], 32) || !(is_aligned_to(sizes[0], 32))))
461         {
462             sizes[0] = align_to(sizes[0], 32);
463             sizes[1] = align_to(sizes[1], 32);
464         }
465         else if (this->format == cldnn::format::os_is_y_x8_osv8_isv4)
466         {
467             sizes[1] = align_to(sizes[1], 4);
468             sizes[0] = align_to(sizes[0], 8);
469             sizes[2] = align_to(sizes[2], 8);
470         }
471         return std::accumulate(
472             sizes.begin(),
473             sizes.end(),
474             static_cast<size_t>(1),
475             std::multiplies<size_t>()
476         );
477     }
478
479     /// Modify padding in layout
480     layout with_padding(padding const& padd) const
481     {
482         layout ret = *this;
483         ret.data_padding = padd;
484         return ret;
485     }
486
487     /// Data type stored in @ref memory (see. @ref data_types)
488     data_types data_type;
489
490     /// Format stored in @ref memory (see. @ref format)
491     cldnn::format format;
492
493     /// The size of the @ref memory (excluding padding)
494     tensor size;
495
496     /// Explicit padding of the @ref memory
497     padding data_padding;
498
499     /// Number of bytes needed to store this layout
500     size_t bytes_count() const { return data_type_traits::size_of(data_type) * get_linear_size(); }
501
502     bool has_fused_format(data_types const& dt, cldnn::format const& fmt) const
503     {
504         return (data_type == dt && format == fmt);
505     }
506
507     auto fused_format() const -> decltype(fuse(data_type, format))
508     {
509         return fuse(data_type, format);
510     }
511 };
512
513
514 /// @}
515 /// @}
516 }