1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 /*!
21  * \file tensor_inspector.h
22  * \brief utility to inspect tensor objects
23  * \author Zhaoqi Zhu
24  */
25 
26 #ifndef MXNET_COMMON_TENSOR_INSPECTOR_H_
27 #define MXNET_COMMON_TENSOR_INSPECTOR_H_
28 
29 #include <algorithm>
30 #include <cmath>
31 #include <string>
32 #include <vector>
33 #include <fstream>
34 #include "../../3rdparty/mshadow/mshadow/base.h"
35 
36 namespace mxnet {
37 
38 /*!
39  * \brief this singleton struct mediates individual TensorInspector objects
40  * so that we can control the global behavior from each of them
41  */
42 struct InspectorManager {
getInspectorManager43   static InspectorManager* get() {
44     static std::mutex mtx;
45     static std::unique_ptr<InspectorManager> im = nullptr;
46     if (!im) {
47       std::unique_lock<std::mutex> lk(mtx);
48       if (!im)
49         im = std::make_unique<InspectorManager>();
50     }
51     return im.get();
52   }
53   /* !\brief mutex used to lock interactive_print() and check_value() */
54   std::mutex mutex_;
55   /* !\brief skip all interactive prints */
56   bool interactive_print_skip_all_ = false;
57   /* !\brief skip all value checks */
58   bool check_value_skip_all_ = false;
59   /* !\brief visit count for interactive print tags */
60   std::unordered_map<std::string, int> interactive_print_tag_counter_;
61   /* !\brief visit count for check value tags */
62   std::unordered_map<std::string, int> check_value_tag_counter_;
63   /* !\brief visit count for dump value tags */
64   std::unordered_map<std::string, int> dump_to_file_tag_counter_;
65 };
66 
67 /*!
68  * \brief Enum for building value checkers for TensorInspector::check_value()
69  */
70 enum CheckerType {
71   NegativeChecker,  // check if is negative
72   PositiveChecker,  // check if is positive
73   ZeroChecker,  // check if is zero
74   NaNChecker,  // check if is NaN, will always return false if DType is not a float type
75   InfChecker,  // check if is infinity, will always return false if DType is not a float type
76   PositiveInfChecker,  // check if is positive infinity,
77                        // will always return false if DType is not a float type
78   NegativeInfChecker,  // check if is nagative infinity,
79                        // will always return false if DType is not a float type
80   FiniteChecker,  // check if is finite, will always return false if DType is not a float type
81   NormalChecker,  // check if is neither infinity nor NaN
82   AbnormalChecker,  // chekck if is infinity or nan
83 };
84 
85 /**
86  *  _______                      _____                           _
87  * |__   __|                    |_   _|                         | |
88  *    | | ___ _ __  ___  ___  _ __| |  _ __  ___ _ __   ___  ___| |_ ___  _ __
89  *    | |/ _ \ '_ \/ __|/ _ \| '__| | | '_ \/ __| '_ \ / _ \/ __| __/ _ \| '__|
90  *    | |  __/ | | \__ \ (_) | | _| |_| | | \__ \ |_) |  __/ (__| || (_) | |
91  *    |_|\___|_| |_|___/\___/|_||_____|_| |_|___/ .__/ \___|\___|\__\___/|_|
92  *                                              | |
93  *                                              |_|
94  */
95 
96 /*!
97  * \brief This class provides a unified interface to inspect the value of all data types
98  * including Tensor, TBlob, and NDArray. If the tensor resides on GPU, then it will be
99  * copied from GPU memory back to CPU memory to be operated on. Internally, all data types
100  * are stored as a TBlob object tb_.
101  */
102 class TensorInspector {
103  private:
104   /*!
105    * \brief generate the tensor info, including data type and shape
106    * \tparam DType the data type
107    * \tparam StreamType the type of the stream object
108    * \param os stream object to output to
109    */
110   template<typename DType, typename StreamType>
tensor_info_to_string(StreamType * os)111   void tensor_info_to_string(StreamType* os) {
112     const int dimension = tb_.ndim();
113     *os << "<" << infer_type_string(typeid(DType)) << " Tensor ";
114     *os << tb_.shape_[0];
115     for (int i = 1; i < dimension; ++i) {
116       *os << 'x' << tb_.shape_[i];
117     }
118     *os << ">" << std::endl;
119   }
120 
121   /*!
122    * \brief output the tensor info, including data type and shape
123    * \tparam DType the data type
124    * \tparam StreamType the type of the stream object
125    * \param os stream object to output to
126    * \param shape the shape of the tensor
127    */
128   template<typename DType, typename StreamType>
tensor_info_to_string(StreamType * os,const std::vector<index_t> & shape)129   void tensor_info_to_string(StreamType* os, const std::vector<index_t>& shape) {
130     const int dimension = shape.size();
131     *os << "<" << infer_type_string(typeid(DType)) << " Tensor ";
132     *os << shape[0];
133     for (int i = 1; i < dimension; ++i) {
134       *os << 'x' << shape[i];
135     }
136     *os << ">" << std::endl;
137   }
138 
139   /*!
140    * \brief output the tensor in a structured format
141    * \tparam DType the data type
142    * \tparam StreamType the type of the stream object
143    * \param os stream object to output to
144    */
145   template<typename DType, typename StreamType>
to_string_helper(StreamType * os)146   void to_string_helper(StreamType* os) {
147 #if MXNET_USE_CUDA
148     if (tb_.dev_mask() == gpu::kDevMask) {
149       TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
150           .to_string_helper<DType>(os);
151       return;
152     }
153 #endif  // MXNET_USE_CUDA
154     const int dimension = tb_.ndim();
155     std::vector<index_t> offsets;
156     index_t multiple = 1;
157     for (int i = dimension - 1; i >= 0; --i) {
158       multiple *= tb_.shape_[i];
159       offsets.push_back(multiple);
160     }
161     *os << std::string(dimension, '[');
162     *os << tb_.dptr<DType>()[0];
163     for (index_t i = 1; i < static_cast<index_t>(tb_.shape_.Size()); ++i) {
164       int n = 0;
165       for (auto off : offsets) {
166         n += (i % off == 0);
167       }
168       if (n) {
169         *os << std::string(n, ']') << ", " <<  std::string(n, '[');
170       } else  {
171         *os << ", ";
172       }
173       *os << tb_.dptr<DType>()[i];
174     }
175     *os << std::string(dimension, ']') << std::endl;
176     tensor_info_to_string<DType>(os);
177   }
178 
179   /*!
180    * \brief output the tensor in a structured format
181    * \tparam DType the data type
182    * \tparam StreamType the type of the stream object
183    * \param os stream object to output to
184    * \param dptr the data pointer
185    */
186   template<typename DType, typename StreamType>
to_string_helper(StreamType * os,const DType * dptr)187   void to_string_helper(StreamType* os, const DType* dptr) {
188 #if MXNET_USE_CUDA
189     if (tb_.dev_mask() == gpu::kDevMask) {
190       TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
191           .to_string_helper<DType>(os, dptr);
192       return;
193     }
194 #endif  // MXNET_USE_CUDA
195     *os << *dptr << std::endl;
196     *os << "<" << typeid(*dptr).name() << ">" << std::endl;
197   }
198 
199   /*!
200    * \brief output a part of the tensor in a structed format
201    * \tparam DType the data type
202    * \tparam StreamType the type of the stream object
203    * \param os stream object to output to
204    * \param sub_shape the sub-shape of the desired part of the tensor
205    * \param offset the position of the first value of the desired part of the tensor
206    */
207   template<typename DType, typename StreamType>
to_string_helper(StreamType * os,const std::vector<index_t> & sub_shape,index_t offset)208   void to_string_helper(StreamType* os, const std::vector<index_t>& sub_shape, index_t offset) {
209 #if MXNET_USE_CUDA
210     if (tb_.dev_mask() == gpu::kDevMask) {
211       TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
212           .to_string_helper<DType>(os, sub_shape, offset);
213       return;
214     }
215 #endif  // MXNET_USE_CUDA
216     DType* dptr = tb_.dptr<DType>() + offset;
217     if (sub_shape.size() == 0) {
218       to_string_helper<DType>(os, dptr);
219       return;
220     }
221     const int dimension = sub_shape.size();
222     std::vector<index_t> offsets;
223     index_t multiple = 1;
224     for (int i = dimension - 1; i >= 0; --i) {
225       multiple *= sub_shape[i];
226       offsets.push_back(multiple);
227     }
228     std::stringstream ss;
229     *os << std::string(dimension, '[');
230     *os << dptr[0];
231     for (index_t i = 1; i < multiple; ++i) {
232       int n = 0;
233       for (auto off : offsets) {
234         n += (i % off == 0);
235       }
236       if (n) {
237         *os << std::string(n, ']') << ", " <<  std::string(n, '[');
238       } else  {
239         *os << ", ";
240       }
241       *os << dptr[i];
242     }
243     *os << std::string(dimension, ']') << std::endl;
244     tensor_info_to_string<DType>(os, sub_shape);
245   }
246 
247   /*!
248    * \brief helper function to calculate the sub_shape and offset for the desired part of the tensor,
249    * given its coordinates in the original tensor
250    * \param pos the coordinates of the desired part of the tensor
251    * \param sub_shape the sub-shape of the desired part of the tensor; calculated here
252    * \param offset the position of the first value of the desired part of the tensor; calculated here
253    */
print_locator(const std::vector<index_t> & pos,std::vector<index_t> * sub_shape,index_t * offset)254   void print_locator(const std::vector<index_t>& pos, std::vector<index_t>* sub_shape,
255       index_t* offset) {
256     const int dimension = tb_.ndim();
257     const int sub_dim = dimension - pos.size();
258     sub_shape->resize(sub_dim);
259     index_t multiple = 1;
260     for (size_t i = pos.size(), j = 0; i < static_cast<size_t>(dimension); ++i, ++j) {
261       (*sub_shape)[j] = tb_.shape_[i];
262       multiple *= tb_.shape_[i];
263     }
264     index_t sum = 0;
265     index_t m = 1;
266     for (index_t i = pos.size() - 1; i >= 0; --i) {
267       sum += pos[i] * m;
268       m *= tb_.shape_[i];
269     }
270     *offset = sum * multiple;
271   }
272 
273   /*!
274    * \brief parse the coordinate of the desired part of the tensor, given a string that represents that
275    * coordinate
276    * \param pos the coordinates of the desired part of the tensor, calculated here
277    * \param str the string that represents the coordinate
278    */
parse_position(std::vector<index_t> * pos,const std::string & str)279   bool parse_position(std::vector<index_t>* pos, const std::string& str) {
280     const int dimension = tb_.ndim();
281     std::istringstream ss(str);
282     index_t n;
283     while (ss >> n) {
284       pos->push_back(n);
285       if (ss.peek() == ',') {
286         ss.ignore();
287       }
288     }
289     if (pos->size() > static_cast<size_t>(dimension)) {
290       return false;
291     }
292     for (size_t i = 0; i < pos->size(); ++i) {
293       if ((*pos)[i] > (tb_.shape_[i] - 1) || (*pos)[i] < 0) {
294         return false;
295       }
296     }
297     return !pos->empty();
298   }
299 
300   /*!
301    * \brief interactive print the tensor value
302    * \tparam DType the data type
303    * \param tag the name given to this call
304    */
305   template<typename DType>
interactive_print_helper(std::string tag)306   void interactive_print_helper(std::string tag) {
307 #if MXNET_USE_CUDA
308     if (tb_.dev_mask() == gpu::kDevMask) {
309       TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
310           .interactive_print_helper<DType>(tag);
311       return;
312     }
313 #endif  // MXNET_USE_CUDA
314     std::lock_guard<std::mutex> lock(InspectorManager::get()->mutex_);
315     InspectorManager::get()->interactive_print_tag_counter_[tag] += 1;
316     while (!InspectorManager::get()->interactive_print_skip_all_) {
317       std::cout << "----------Interactive Print----------" << std::endl;
318       if (tag != "") {
319         std::cout << "Tag: " << tag << "  Visit: " <<
320             InspectorManager::get()->interactive_print_tag_counter_[tag] <<  std::endl;
321       }
322       tensor_info_to_string<DType>(&std::cout);
323       std::cout << "To print a part of the tensor, " <<
324           "please specify a position, seperated by \",\"" << std::endl;
325       std::cout << "\"e\" for the entire tensor, " <<
326           "\"d\" to dump value to file, " <<
327           "\"b\" to break, " <<
328           "\"s\" to skip all: ";
329       std::string str;
330       std::cin >> str;
331       if (str == "b") {
332         break;
333       } else if (str == "e") {
334         to_string_helper<DType>(&std::cout);
335         continue;
336       } else if (str == "s") {
337         InspectorManager::get()->interactive_print_skip_all_ = true;
338         break;
339       } else if (str == "d") {
340         while (true) {
341           std::cout << "Please enter a tag: ";
342           std::cin >> str;
343           if (str.find(' ') != std::string::npos) {
344             std::cout << "Invalid tag name. No space allowed.";
345             continue;
346           }
347           dump_to_file_helper<DType>(str);
348           break;
349         }
350         continue;
351       }
352       std::vector<index_t> pos;
353       if (parse_position(&pos, str)) {
354         std::vector<index_t> sub_shape;
355         index_t offset;
356         print_locator(pos, &sub_shape, &offset);
357         to_string_helper<DType>(&std::cout, sub_shape, offset);
358       } else {
359         std::cout << "invalid command/indices" << std::endl;
360       }
361     }
362   }
363 
364   /*!
365    * \brief build the lambda function, aka the checker, given its type
366    * \tparam DType the data type
367    * \param ct the type of the checker
368    */
369   template<typename DType>
get_checker(CheckerType ct)370   std::function<bool(DType)> get_checker(CheckerType ct) {
371     switch (ct) {
372       case NegativeChecker:
373         return [] (DType x) {
374               return x < 0;
375             };
376       case PositiveChecker:
377         return [] (DType x) {
378               return x > 0;
379             };
380       case ZeroChecker:
381         return [] (DType x) {
382               return x == 0;
383             };
384       case NaNChecker:
385         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
386             std::is_same<DType, mshadow::half::half_t>::value) {
387           return [] (DType x) {
388                 return x != x;
389               };
390         } else {
391           LOG(WARNING) << "NaNChecker only applies to float types. " <<
392               "Lambda will always return false.";
393         }
394         break;
395       case InfChecker:
396         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
397             std::is_same<DType, mshadow::half::half_t>::value) {
398           return [] (DType x) {
399                 return x == (DType)1.0 / 0.0f || x == -(DType)1.0 / 0.0f;
400               };
401         } else {
402           LOG(WARNING) << "InfChecker only applies to float types. " <<
403               "Lambda will always return false.";
404         }
405         break;
406       case PositiveInfChecker:
407         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
408             std::is_same<DType, mshadow::half::half_t>::value) {
409           return [] (DType x) {
410                 return x == (DType)1.0 /  0.0f;
411               };
412         } else {
413           LOG(WARNING) << "PositiveInfChecker only applies to float types. " <<
414               "Lambda will always return false.";
415         }
416         break;
417       case NegativeInfChecker:
418         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
419             std::is_same<DType, mshadow::half::half_t>::value) {
420           return [] (DType x) {
421                 return x == -(DType)1.0 /  0.0f;
422               };
423         } else {
424           LOG(WARNING) << "NegativeInfChecker only applies to float types. " <<
425               "Lambda will always return false.";
426         }
427         break;
428       case FiniteChecker:
429         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
430             std::is_same<DType, mshadow::half::half_t>::value) {
431           return [] (DType x) {
432                 return x != (DType)1.0 /  0.0f && x != -(DType)1.0 /  0.0f;
433               };
434         } else {
435           LOG(WARNING) << "FiniteChecker only applies to float types. " <<
436               "Lambda will always return false.";
437         }
438         break;
439       case NormalChecker:
440         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
441             std::is_same<DType, mshadow::half::half_t>::value) {
442           return [] (DType x) {
443                 return x != (DType)1.0 /  0.0f && x != -(DType)1.0 /  0.0f &&
444                     x == x;
445               };
446         } else {
447           LOG(WARNING) << "NormalChecker only applies to float types. " <<
448               "Lambda will always return false.";
449         }
450         break;
451       case AbnormalChecker:
452         if (std::is_same<DType, float>::value || std::is_same<DType, double>::value ||
453             std::is_same<DType, mshadow::half::half_t>::value) {
454           return [] (DType x) {
455                 return x == (DType)1.0 /  0.0f || x == -(DType)1.0 /  0.0f ||
456                     x != x;
457               };
458         } else {
459           LOG(WARNING) << "AbnormalChecker only applies to float types. " <<
460               "Lambda will always return false.";
461         }
462         break;
463       default:
464         return [] (DType x) {
465               return false;
466             };
467     }
468     return [] (DType x) {return false;};
469   }
470 
471   /*!
472    * \brief calculate the coordinate of a value in the tensor, given its index
473    * \param idx the index of the value in the tensor
474    */
index_to_coordinates(index_t idx)475   std::vector<index_t> index_to_coordinates(index_t idx) {
476     const int dimension = tb_.ndim();
477     std::vector<index_t> ret;
478     for (int i = dimension - 1; i >= 0; --i) {
479       ret.push_back(idx % tb_.shape_[i]);
480       idx /= tb_.shape_[i];
481     }
482     std::reverse(ret.begin(), ret.end());
483     return ret;
484   }
485 
486   /*!
487    * \brief check/validate the values within the tensor, find the coordinates
488    * where the value checker evaluates to true
489    * \tparam DType the data type
490    * \param ret a vector of coordinates which itself is a vector of int; calculated here
491    * \param checker the lambda function to check each value of within the tensor
492    * \param interactive wherether to allow the user to interactively check the coordinates
493    * \param tag the name given to this call
494    */
495   template<typename DType>
check_value_helper(std::vector<std::vector<index_t>> * ret,const std::function<bool (DType)> & checker,bool interactive,std::string tag)496   void check_value_helper(std::vector<std::vector<index_t>>* ret,
497       const std::function<bool(DType)>& checker, bool interactive, std::string tag) {
498 #if MXNET_USE_CUDA
499     if (tb_.dev_mask() == gpu::kDevMask) {
500       return TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
501           .check_value_helper<DType>(ret, checker, interactive, tag);
502     }
503 #endif  // MXNET_USE_CUDA
504     index_t count = 0;
505     std::stringstream ss;
506     ss << "[";
507     bool first_pass = true;
508     for (index_t i = 0; i <static_cast<index_t>(tb_.shape_.Size()); ++i) {
509       if (checker(tb_.dptr<DType>()[i])) {
510         ++count;
511         if (!first_pass) {
512           ss  << ", ";
513         }
514         first_pass = false;
515         std::vector<index_t> coords = index_to_coordinates(i);
516         ss << "(" << coords[0];
517         for (size_t i = 1; i < coords.size(); ++i) {
518           ss << ", " << coords[i];
519         }
520         ss << ")";
521         ret->push_back(coords);
522       }
523     }
524     ss << "]" << std::endl;
525     if (interactive) {
526       std::lock_guard<std::mutex> lock(InspectorManager::get()->mutex_);
527        InspectorManager::get()->check_value_tag_counter_[tag] += 1;
528       while (!InspectorManager::get()->check_value_skip_all_) {
529         std::cout << "----------Value Check----------" << std::endl;
530         tensor_info_to_string<DType>(&std::cout);
531         if (tag != "") {
532           std::cout << "Tag: " << tag << "  Visit: " <<
533               InspectorManager::get()->check_value_tag_counter_[tag] <<  std::endl;
534         }
535         std::cout << count << " value(s) found." << std::endl;
536         std::cout << "To print a part of the tensor," <<
537             " please specify a position, seperated by \",\"" << std::endl;
538         std::cout << "\"e\" for the entire tensor, " <<
539             "\"p\" to print the coordinates of the values found, " <<
540             "\"b\" to break, " <<
541             "\"s\" to skip all: ";
542         std::string str;
543         std::cin >> str;
544         if (str == "b") {
545           break;
546         } else if (str == "e") {
547           to_string_helper<DType>(&std::cout);
548           continue;
549         } else if (str == "p") {
550           std::cout << ss.str() << std::endl;
551           continue;
552         } else if (str == "s") {
553           InspectorManager::get()->check_value_skip_all_ = true;
554           break;
555         }
556         std::vector<index_t> pos;
557         if (parse_position(&pos, str)) {
558           std::vector<index_t> sub_shape;
559           index_t offset;
560           print_locator(pos, &sub_shape, &offset);
561           to_string_helper<DType>(&std::cout, sub_shape, offset);
562         } else {
563           std::cout << "invalid command/indices" << std::endl;
564         }
565       }
566     }
567   }
568 
569   /*!
570    * \brief infer the python type, given the c++ type
571    * \tparam ti the type info
572    */
infer_type(const std::type_info & ti)573   inline char infer_type(const std::type_info& ti) {
574     if (ti == typeid(float)) return 'f';
575     else if (ti == typeid(double)) return 'f';
576     else if (ti == typeid(mshadow::half::half_t) ) return 'f';
577     else if (ti == typeid(uint8_t)) return 'u';
578     else if (ti == typeid(int32_t)) return 'i';
579     else if (ti == typeid(int64_t)) return 'i';
580     else
581       return '?';
582   }
583 
584   /*!
585    * \brief infer the python type, given the c++ type
586    * \tparam ti the type info
587    */
infer_type_string(const std::type_info & ti)588   inline std::string infer_type_string(const std::type_info& ti) {
589     if (ti == typeid(float)) return "float";
590     else if (ti == typeid(double)) return "double";
591     else if (ti == typeid(mshadow::half::half_t) ) return "mshasow::half::half_t";
592     else if (ti == typeid(uint8_t)) return "uint8_t";
593     else if (ti == typeid(int32_t)) return "int32_t";
594     else if (ti == typeid(int64_t)) return "int64_t";
595     else
596       return "unknown tyoe";
597   }
598 
599   /*!
600    * \brief check if the host machine is big or small endian
601    */
endian_test()602   inline char endian_test() {
603     int x = 1;
604     return (reinterpret_cast<char*>(&x)[0]) ? '<' : '>';
605   }
606 
607   /*!
608    * \brief generate the header following npy 1.0 format
609    * \tparam DType the data type
610    */
611   template<typename DType>
get_header()612   std::string get_header() {
613     const int dimension = tb_.ndim();
614     std::string dict;
615     dict += "{'descr':'";
616     dict += endian_test();
617     dict += infer_type(typeid(DType));
618     dict += std::to_string(sizeof(DType));
619     dict += "','fortran_order':False,'shape':(";
620     dict += std::to_string(tb_.shape_[0]);
621     for (int i = 1; i < dimension; ++i) {
622       dict += ',';
623       dict += std::to_string(tb_.shape_[i]);
624     }
625     if (dimension == 1) {
626        dict += ",";
627     }
628     dict += ")} ";
629     int padding_size = 64 - ((10 + dict.size()) % 64);
630     dict += std::string(padding_size, ' ');
631     dict.back() = '\n';
632     std::string header;
633     header += static_cast<char>(0x93);
634     header += "NUMPY";
635     header += static_cast<char>(0x01);
636     header += static_cast<char>(0x00);
637     header += static_cast<char>((uint16_t)dict.size() & 0x00ff);
638     header += static_cast<char>(((uint16_t)dict.size() >> 8) & 0x00ff);
639     header += dict;
640     return header;
641   }
642 
643   /*!
644    * \brief write the header and the date to an npy file
645    * \tparam DType the data type
646    * \param header the header of the file
647    * \param filename the file name
648    */
649   template<typename DType>
write_npy(const std::string & header,const std::string & filename)650   void write_npy(const std::string& header, const std::string& filename) {
651     std::ofstream file;
652     file.exceptions(std::ofstream::failbit | std::ofstream::badbit);
653     try {
654       file.open(filename, std::ios::out | std::ios::binary);
655       file.write(header.c_str(), header.size());
656       file.write(reinterpret_cast<char*>(tb_.dptr<DType>()), sizeof(DType) * tb_.shape_.Size());
657       file.close();
658       std::cout << "Tensor dumped to file: " << filename << std::endl;
659     } catch (std::ofstream::failure e) {
660       std::cerr << "Exception opening/writing/closing file " << filename << std::endl;
661     }
662   }
663 
664   /*!
665    * \brief dump the value of the tensor to a file with name "[tag]_[visit count].npy" in npy format
666    * the dump file follows npy 1.0 stantand
667    * \tparam DType the data type
668    * \param tag the name given to this call
669    */
670   template<typename DType>
dump_to_file_helper(const std::string & tag)671   void dump_to_file_helper(const std::string& tag) {
672 #if MXNET_USE_CUDA
673     if (tb_.dev_mask() == gpu::kDevMask) {
674       TensorInspector(test::CAccessAsCPU(ctx_, tb_, false)(), ctx_)
675           .dump_to_file_helper<DType>(tag);
676       return;
677     }
678 #endif  // MXNET_USE_CUDA
679     std::string header = get_header<DType>();
680     InspectorManager::get()->dump_to_file_tag_counter_[tag] += 1;
681     const int visit = InspectorManager::get()->dump_to_file_tag_counter_[tag];
682     std::string filename = tag + "_" + std::to_string(visit) + ".npy";
683     write_npy<DType>(header, filename);
684   }
685 
686    /*!
687    * \brief validate that the shape
688    */
validate_shape()689   inline void validate_shape() {
690     const int dimension = tb_.ndim();
691     CHECK(dimension > 0) << "Tensor Inspector does not support empty tensors " <<
692         "or tensors of unknow shape.";
693     for (int i = 0; i < dimension; ++i) {
694       CHECK(tb_.shape_[i] != 0) << "Invalid tensor shape: shape_[" << i << "] is 0";
695     }
696   }
697 
698   /* !\brief the tensor blob */
699   const TBlob tb_;
700   /* !\brief the run context of the tensor */
701   const RunContext& ctx_;
702 
703  public:
704    /*!
705    * \brief construct from Tensor object
706    * \tparam Device the device the tensor resides in
707    * \tparam dimension the dimension of the tensor
708    * \tparam DType the data type
709    * \param ts the source tensor object
710    * \param ctx the run context of the tensor
711    */
712   template<typename Device, int dimension, typename DType>
TensorInspector(const mshadow::Tensor<Device,dimension,DType> & ts,const RunContext & ctx)713   TensorInspector(const mshadow::Tensor<Device, dimension, DType>& ts, const RunContext& ctx):
714       tb_(ts), ctx_(ctx) {
715     validate_shape();
716   }
717 
718   /*!
719    * \brief construct from TBlob object
720    * \param tb the source tblob object
721    * \param ctx the run context of the tensor
722    */
TensorInspector(const TBlob & tb,const RunContext & ctx)723   TensorInspector(const TBlob& tb, const RunContext& ctx):
724       tb_(tb), ctx_(ctx) {
725     validate_shape();
726   }
727 
728   /*!
729    * \brief construct from NDArray object. Currently this only works with kDefaultStorage
730    * \param arr the source ndarray object
731    * \param ctx the run context of the tensor
732    */
TensorInspector(const NDArray & arr,const RunContext & ctx)733   TensorInspector(const NDArray& arr, const RunContext& ctx):
734       tb_(arr.data()), ctx_(ctx) {
735     validate_shape();
736   }
737 
738   /*!
739    * \brief print the tensor to std::cout
740    */
print_string()741   void print_string() {
742     std::cout << to_string() << std::endl;
743   }
744 
745   /*!
746    * \brief return a string which contains the values and other info of the tensor
747    */
to_string()748   std::string to_string() {
749     std::stringstream ss;
750     MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, {
751       to_string_helper<DType>(&ss);
752     });
753     return ss.str();
754   }
755 
756   /*!
757    * \brief interactively print the tensor value
758    * \param tag the name given to this call
759    */
760   void interactive_print(std::string tag = "") {
761     MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, {
762       interactive_print_helper<DType>(tag);
763     });
764   }
765 
766   /*!
767    * \brief check/validate the values within the tensor, return the coordinates
768    * where the value checker evaluates to true
769    * \tparam ValueChecker the type of the lambda
770    * \param checker the lambda function to check each value of within the tensor
771    * \param interactive wherether to allow the user to interactively check the coordinates
772    * \param tag the name given to this call
773    */
774   template<typename ValueChecker>
775   std::vector<std::vector<index_t>> check_value(const ValueChecker& checker,
776       bool interactive = false, std::string tag = "") {
777     std::vector<std::vector<index_t>> ret;
778     MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, {
779       check_value_helper<DType>(&ret, checker, ret, interactive, tag);
780     });
781     return ret;
782   }
783 
784   /*!
785    * \brief check/validate the values within the tensor, return the coordinates
786    * where the lambda evaluates to true
787    * \param ct the type of the checker
788    * \param interactive wherether to allow the user to interactively check the coordinates
789    * \param tag the name given to this call
790    */
791   std::vector<std::vector<index_t>> check_value(CheckerType ct, bool interactive = false,
792       std::string tag = "") {
793     std::vector<std::vector<index_t>> ret;
794     MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, {
795       check_value_helper<DType>(&ret, get_checker<DType>(ct), interactive, tag);
796     });
797     return ret;
798   }
799 
800   /*!
801    * \brief dump the value of the tensor to a file with name "tag_[visit count].npy" in npy format
802    * \param tag the name given to this call
803    */
dump_to_file(std::string tag)804   void dump_to_file(std::string tag) {
805     MSHADOW_TYPE_SWITCH(tb_.type_flag_, DType, {
806       dump_to_file_helper<DType>(tag);
807     });
808   }
809 };
810 
811 }  // namespace mxnet
812 
813 #endif  // MXNET_COMMON_TENSOR_INSPECTOR_H_
814