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