Caffe2 - C++ API
A deep learning, cross platform ML framework
tensor.h
1 #ifndef CAFFE2_CORE_TENSOR_H_
2 #define CAFFE2_CORE_TENSOR_H_
3 
4 #include <cstddef>
5 #include <cstdint>
6 #include <fstream>
7 #include <sstream>
8 #include <type_traits>
9 #include <typeinfo>
10 #include <vector>
11 
12 #include "caffe2/core/common.h"
13 #include "caffe2/core/flags.h"
14 #include "caffe2/core/context.h"
15 #include "caffe2/core/typeid.h"
16 #include "caffe2/core/logging.h"
17 
18 // A global boolean variable to control whether we free memory when a Tensor
19 // is shrinked to a smaller size. As a result, a Tensor is always going to
20 // keep the memory allocated for its maximum capacity reshaped to so far.
21 CAFFE2_DECLARE_bool(caffe2_keep_on_shrink);
22 
23 namespace caffe2 {
24 
28 inline vector<TIndex> ToVectorTIndex(const std::vector<int>& src) {
29  return vector<TIndex>(src.begin(), src.end());
30 }
31 
35 inline TIndex size_from_dim_(int k, vector<TIndex> dims) {
36  TIndex r = 1;
37  for (int i = k; i < dims.size(); ++i) {
38  r *= dims[i];
39  }
40  return r;
41 }
42 
43 // Product of all dims up to
44 inline TIndex size_to_dim_(int k, vector<TIndex> dims) {
45  CAFFE_ENFORCE(k < dims.size());
46  TIndex r = 1;
47  for (int i = 0; i < k; ++i) {
48  r *= dims[i];
49  }
50  return r;
51 }
52 
53 inline int canonical_axis_index_(int axis_index, int ndims) {
54  CAFFE_ENFORCE_GE(axis_index, -ndims);
55  CAFFE_ENFORCE_LT(axis_index, ndims);
56  if (axis_index < 0) {
57  return axis_index + ndims;
58  }
59  return axis_index;
60 }
61 
62 
72 template <class Context>
73 class Tensor {
74  public:
78  Tensor() {}
79 
86  explicit Tensor(const vector<TIndex>& dims) { Resize(dims); }
87  explicit Tensor(const vector<int>& dims) { Resize(dims); }
88 
99  template <class SrcContext, class ContextForCopy>
100  Tensor(const Tensor<SrcContext>& src, ContextForCopy* context) {
101  CopyFrom(src, context);
102  }
103 
111  template <class SrcContext>
113  CopyFrom(src);
114  }
115 
119  template <typename T>
120  Tensor(const vector<TIndex>& dims, const vector<T>& values, Context* context)
121  : meta_(TypeMeta::Make<T>()) {
122  Resize(dims);
123  CAFFE_ENFORCE_EQ(values.size(), size_);
124  context->template Copy<T, CPUContext, Context>(size_, values.data(), mutable_data<T>());
125  }
126 
130  template <typename T,
131  typename = typename std::enable_if<std::is_scalar<T>::value>::type>
132  Tensor(const T& value, Context* context) {
133  Resize(vector<TIndex>{});
134  context->template Copy<T, CPUContext, Context>(size_, &value, mutable_data<T>());
135  }
136 
141  template <class SrcContext, class ContextForCopy>
142  void CopyFrom(const Tensor<SrcContext>& src, ContextForCopy* context) {
143  if ((void*)&src == (void*)this) {
144  return;
145  }
146  meta_ = src.meta();
147  Resize(src.dims());
148  if (size() > 0) {
149  if (meta_.copy()) {
150  meta_.copy()(src.raw_data(), raw_mutable_data(), size());
151  } else {
152  context->template CopyBytes<SrcContext, Context>(
153  nbytes(), src.raw_data(), raw_mutable_data());
154  }
155  }
156  }
157 
165  template <class SrcContext>
166  inline void CopyFrom(const Tensor<SrcContext>& src) {
167  SrcContext tmp_context;
168  CopyFrom(src, &tmp_context);
169  }
170 
171  virtual ~Tensor() noexcept {}
172 
182  template <class ContextForCopy>
183  void Extend(TIndex num, float growthPct, ContextForCopy* context) {
184  CAFFE_ENFORCE_GE(dims_.size(), 1);
185  auto newDims = dims_;
186  newDims[0] += num;
187  if (!data_) {
188  Resize(newDims);
189  return;
190  }
191  auto newSize = std::accumulate(
192  newDims.begin(),
193  newDims.end(),
194  static_cast<TIndex>(1),
195  std::multiplies<TIndex>());
196  if (newSize * meta_.itemsize() <= capacity_) {
197  dims_ = newDims;
198  size_ = newSize;
199  return;
200  }
201  auto newCapacity = dims_;
202  newCapacity[0] = std::max<size_t>(
203  newDims[0], std::ceil(dims_[0] * (growthPct + 100) / 100));
204  Reserve(newCapacity, context);
205  dims_ = newDims;
206  size_ = newSize;
207  }
208 
209  template <class T, class ContextForCopy>
210  void Reserve(const std::vector<T>& newCapacity, ContextForCopy* context) {
211  auto newSize = std::accumulate(
212  newCapacity.begin(),
213  newCapacity.end(),
214  static_cast<TIndex>(1),
215  std::multiplies<TIndex>());
216  if (newSize * meta_.itemsize() <= capacity_) {
217  return;
218  }
219  auto oldData = std::move(data_);
220  auto oldSize = size_;
221  auto oldDims = dims_;
222  Resize(newCapacity);
223  auto* newData = raw_mutable_data(meta_);
224  context->template CopyItems<ContextForCopy, ContextForCopy>(
225  meta_, oldSize, oldData.get(), newData);
226  dims_ = oldDims;
227  size_ = oldSize;
228  }
229 
236  void Shrink(TIndex outer_dim) {
237  CAFFE_ENFORCE(dims_.size() >= 1, "Tensor must be at least 1D");
238  CAFFE_ENFORCE(
239  outer_dim <= dims_[0],
240  "New outer dimension must be smaller than current.");
241  dims_[0] = outer_dim;
242  size_ = std::accumulate(
243  dims_.begin(),
244  dims_.end(),
245  static_cast<TIndex>(1),
246  std::multiplies<TIndex>());
247  }
248 
262  template <typename... Ts>
263  void Resize(Ts... dim_source) {
264  bool size_changed = SetDims(dim_source...);
265  // If needed, we will free the data. the next mutable_data() call
266  // will create the data storage.
267  if (size_changed && (capacity_ < size_ * meta_.itemsize() ||
268  !FLAGS_caffe2_keep_on_shrink)) {
269  data_.reset();
270  capacity_ = 0;
271  }
272  }
273 
278  template <class OtherContext>
279  inline void ResizeLike(const Tensor<OtherContext>& src_tensor) {
280  // Note: need casting for different context types.
281  if (static_cast<void*>(this) != static_cast<const void*>(&src_tensor)) {
282  Resize(src_tensor.dims());
283  }
284  }
285 
290  inline void Reshape(const vector<TIndex>& dims) {
291  TIndex new_size = 1;
292  for (auto d : dims) {
293  CAFFE_ENFORCE_GE(d, 0);
294  new_size *= d;
295  }
296  CAFFE_ENFORCE(
297  new_size == size_,
298  "New size and old size are not equal. You cannot use Reshape, "
299  "but should use Resize."
300  // TODO(jiayq): remove the following warning after pending diffs
301  // stabilize.
302  " The old caffe2 mixes Reshape and Resize but this behavior has "
303  "been changed. If you find this error, most likely you will need "
304  "to change corresponding code from Reshape to Resize.");
305  dims_ = dims;
306  }
307 
308  inline void Reshape(const vector<int>& dims) {
309  Reshape(ToVectorTIndex(dims));
310  }
311 
317  string DebugString() const {
318  std::stringstream ss;
319  ss << "A Tensor of item size " << itemsize() << " and type "
320  << meta_.name() << " and dimension (";
321  for (int d : dims_) {
322  ss << d << ",";
323  }
324  ss << ").";
325  return ss.str();
326  }
327 
340  void ShareData(const Tensor& src) {
341  meta_ = src.meta();
342  CAFFE_ENFORCE_EQ(
343  src.size_,
344  size_,
345  "Size mismatch - did you call reshape before sharing the data?");
346  // It is possible that the source tensor hasn't called mutable_data() yet,
347  // in which case ShareData() doesn't make much sense since we don't really
348  // know what to share yet.
349  CAFFE_ENFORCE(
350  src.data_.get() || src.size_ == 0,
351  "Source tensor has no content and has size > 0");
352  // Finally, do sharing.
353  data_ = src.data_;
354  capacity_ = src.capacity_;
355  shares_data_ = true;
356  }
357 
366  template <typename T>
367  void ShareExternalPointer(T* src, size_t capacity = 0) {
368  ShareExternalPointer(src, capacity, [](void*) -> void {});
369  }
370 
377  template <typename T, typename Deleter>
378  void ShareExternalPointer(T* src, size_t capacity, Deleter&& d) {
380  src, TypeMeta::Make<T>(), capacity, std::forward<Deleter>(d));
381  }
382 
383  void
384  ShareExternalPointer(void* src, const TypeMeta& meta, size_t capacity = 0) {
385  ShareExternalPointer(src, meta, capacity, [](void*) -> void {});
386  }
387 
388  template <class Deleter>
390  void* src,
391  const TypeMeta& meta,
392  size_t capacity,
393  Deleter&& d) {
394  meta_ = meta;
395  CAFFE_ENFORCE_WITH_CALLER(
396  meta_.id(),
397  "To share with a raw external pointer you need to have meta "
398  "already set.");
399  CAFFE_ENFORCE_WITH_CALLER(
400  size_ > 0,
401  "To share data with a raw pointer, you need to set shape first.");
402  data_.reset(src, std::forward<Deleter>(d));
403  // Sets capacity. If not specified, we will implicitly assume that
404  // the capacity is the current size.
405  if (capacity) {
406  capacity_ = capacity;
407  } else {
408  capacity_ = nbytes();
409  }
410  shares_data_ = true;
411  }
412 
413  bool shares_data() {
414  return shares_data_;
415  }
416 
421  inline const void* raw_data() const {
422  CAFFE_ENFORCE_WITH_CALLER(data_.get() || size_ == 0);
423  return data_.get();
424  }
425 
432  template <typename T>
433  inline const T* data() const {
434  CAFFE_ENFORCE_WITH_CALLER(
435  data_.get() || size_ == 0,
436  "The tensor is of non-zero shape, but its data is not allocated yet. "
437  "Caffe2 uses a lazy allocation, so you will need to call "
438  "mutable_data() or raw_mutable_data() to actually allocate memory.");
439  CAFFE_ENFORCE_WITH_CALLER(
440  IsType<T>(),
441  "Tensor type mismatch, caller expects elements to be ",
442  TypeMeta::Name<T>(),
443  " while tensor contains ",
444  meta_.name());
445  return static_cast<T*>(data_.get());
446  }
447 
459  inline void* raw_mutable_data(const TypeMeta& meta) {
460  // For 0-size tensors it's fine to return any pointer (including nullptr)
461  if (meta_ == meta && (data_.get() || size_ == 0)) {
462  return data_.get();
463  } else {
464  meta_ = meta;
465  CAFFE_ENFORCE_WITH_CALLER(
466  size_ >= 0,
467  "Tensor is not initialized. You probably need to call Resize() "
468  "before calling mutable_data()");
469  if (size_ == 0) {
470  return data_.get();
471  }
472  if (meta.ctor()) {
473  // For types that need placement new, we will call it, as well as
474  // making sure that when the data is freed, it calls the right
475  // destruction procedure.
476  auto size = size_;
477  auto dtor = meta_.dtor();
478  data_.reset(
479  static_cast<void*>(Context::New(size_ * meta_.itemsize())),
480  [size, dtor](void* ptr) -> void {
481  dtor(ptr, size);
482  Context::Delete(ptr);
483  });
484  meta_.ctor()(data_.get(), size_);
485  } else {
486  // For fundamental type, new and delete is easier.
487  data_.reset(static_cast<void*>(Context::New(size_ * meta_.itemsize())),
488  Context::Delete);
489  }
490  capacity_ = size_ * meta_.itemsize();
491  return data_.get();
492  }
493  }
494 
504  inline void* raw_mutable_data() {
505  CAFFE_ENFORCE_WITH_CALLER(
506  meta_.id() != 0,
507  "Calling raw_mutable_data() without meta, but the current meta is "
508  "of unknown type.");
509  return raw_mutable_data(meta_);
510  }
511 
518  template <typename T>
519  inline T* mutable_data() {
520  if ((size_ == 0 || data_.get()) && IsType<T>()) {
521  return static_cast<T*>(data_.get());
522  }
523  return static_cast<T*>(raw_mutable_data(TypeMeta::Make<T>()));
524  }
525 
526 
530  inline int ndim() const { return dims_.size(); }
534  inline TIndex size() const { return size_; }
538  inline size_t itemsize() const { return meta_.itemsize(); }
544  inline size_t nbytes() const { return size_ * meta_.itemsize(); }
545 
546  inline size_t capacity_nbytes() const {
547  return capacity_;
548  }
552  inline const vector<TIndex>& dims() const { return dims_; }
553 
554  inline TIndex size_from_dim(int k) const {
555  return size_from_dim_(k, dims_);
556  }
557 
558  inline TIndex size_to_dim(int k) const {
559  return size_to_dim_(k, dims_);
560  }
561 
573  inline int canonical_axis_index(int axis_index) const {
574  return canonical_axis_index_(axis_index, ndim());
575  }
576 
580  template <typename T>
581  inline bool IsType() const { return meta_.Match<T>(); }
585  inline const TypeMeta& meta() const { return meta_; }
586 
594  inline int dim32(const int i) const {
595  DCHECK_LT(i, dims_.size()) << "Exceeding ndim limit " << dims_.size();
596  DCHECK_GE(i, 0) << "Cannot have negative index";
597  CAFFE_ENFORCE_LT(dims_[i], std::numeric_limits<int>::max());
598  return static_cast<int>(dims_[i]);
599  }
600 
606  inline TIndex dim(const int i) const {
607  DCHECK_LT(i, dims_.size()) << "Exceeding ndim limit " << dims_.size();
608  DCHECK_GE(i, 0) << "Cannot have negative index";
609  return dims_[i];
610  }
611 
612  protected:
613  vector<TIndex> dims_;
614  TIndex size_ = -1;
615  TypeMeta meta_;
616  std::shared_ptr<void> data_;
617  bool shares_data_ = false;
618  size_t capacity_ = 0;
619  // In case of chunk load we store how much data was already loaded
620 
621  private:
622  template <
623  typename T,
624  typename = typename std::enable_if<std::is_integral<T>::value>::type>
625  bool SetDims(const vector<T>& src) {
626  auto old_size = size_;
627  dims_.resize(src.size());
628  TIndex new_size = 1;
629  for (unsigned int i = 0; i < src.size(); ++i) {
630  new_size *= src[i];
631  dims_[i] = src[i];
632  }
633  size_ = new_size;
634  return size_ != old_size;
635  }
636 
637  bool SetDims() {
638  auto old_size = size_;
639  dims_.resize(0);
640  size_ = 1;
641  return size_ != old_size;
642  }
643 
644  // TODO(jiayq): maybe rewrite the following functions with initializer list.
645  // NVCC does not play well with initializer lists last time, but worth
646  // another shot.
647  bool SetDims(const TIndex d0) {
648  auto old_size = size_;
649  dims_.resize(1);
650  dims_[0] = d0;
651  size_ = d0;
652  return size_ != old_size;
653  }
654 
655  bool SetDims(const TIndex d0, const TIndex d1) {
656  auto old_size = size_;
657  dims_.resize(2);
658  dims_[0] = d0;
659  dims_[1] = d1;
660  size_ = d0 * d1;
661  return size_ != old_size;
662  }
663 
664  bool SetDims(const TIndex d0, const TIndex d1, const TIndex d2) {
665  auto old_size = size_;
666  dims_.resize(3);
667  dims_[0] = d0;
668  dims_[1] = d1;
669  dims_[2] = d2;
670  size_ = d0 * d1 * d2;
671  return size_ != old_size;
672  }
673 
674  bool
675  SetDims(const TIndex d0, const TIndex d1, const TIndex d2, const TIndex d3) {
676  auto old_size = size_;
677  dims_.resize(4);
678  dims_[0] = d0;
679  dims_[1] = d1;
680  dims_[2] = d2;
681  dims_[3] = d3;
682  size_ = d0 * d1 * d2 * d3;
683  return size_ != old_size;
684  }
685 
686  // Note(jiayq): possibly a rule-of-three violation, but we explicitly
687  // discourage the use of = for Tensors.
688  Tensor& operator=(const Tensor& src) = delete;
689 };
690 
691 // For simplicity, we will typedef Tensor<CPUContext> to TensorCPU.
693 
694 constexpr int k_limit_default_ = 1000;
695 
696 // Type call registry
697 typedef TypeMeta (*TypeCall)(void*);
698 TypeCall GetTypeCallFunction(CaffeTypeId id);
699 void RegisterTypeCallFunction(CaffeTypeId id, TypeCall c);
700 
701 template <class Context>
702 TypeMeta GetTensorType(void* c) {
703  Tensor<Context>* tc = static_cast<Tensor<Context>*>(c);
704  return tc->meta();
705 }
706 
707 // Shape call registry
708 typedef vector<TIndex> (*ShapeCall)(void*, bool& shares_data, size_t& capacity);
709 ShapeCall GetShapeCallFunction(CaffeTypeId id);
710 void RegisterShapeCallFunction(CaffeTypeId id, ShapeCall c);
711 
712 template <class Context>
713 vector<TIndex> GetTensorShape(void* c, bool& shares_data, size_t& capacity) {
714  Tensor<Context>* tc = static_cast<Tensor<Context>*>(c);
715  shares_data = tc->shares_data();
716  capacity = tc->capacity_nbytes();
717  return tc->dims();
718 }
719 
721  public:
722  explicit TensorPrinter(
723  const std::string& tensor_name = "",
724  const std::string& file_name = "",
725  int limit = k_limit_default_);
726  ~TensorPrinter();
727 
728  template <class T>
729  void Print(const Tensor<CPUContext>& tensor);
730 
731  template <class Context>
732  void PrintMeta(const Tensor<Context>& tensor);
733 
734  private:
735  string MetaStr(const Tensor<CPUContext>& tensor);
736 
737  private:
738  bool to_file_;
739  int limit_;
740  std::unique_ptr<std::ofstream> log_file_;
741  std::string tensor_name_;
742 };
743 
744 template <class T>
745 void TensorPrinter::Print(const Tensor<CPUContext>& tensor) {
746  std::stringstream values_stream;
747  // One most likely doesn't want to print int64-number of items for visual
748  // inspection, so we cast down to int here.
749  int total_count = std::min(tensor.size(), TIndex(limit_));
750  const T* tensor_data = tensor.template data<T>();
751  for (int i = 0; i < total_count - 1; ++i) {
752  values_stream << tensor_data[i] << ",";
753  }
754  // We do not add a comma after the last item.
755  values_stream << tensor_data[total_count - 1];
756  if (to_file_) {
757  (*log_file_) << MetaStr(tensor) << values_stream.str() << std::endl;
758  } else {
759  // Log to console.
760  LOG(INFO) << MetaStr(tensor) << values_stream.str();
761  }
762 }
763 
764 template <class Context>
765 void TensorPrinter::PrintMeta(const Tensor<Context>& tensor) {
766  if (to_file_) {
767  (*log_file_) << MetaStr(tensor) << std::endl;
768  } else {
769  LOG(INFO) << MetaStr(tensor);
770  }
771 }
772 
773 } // namespace caffe2
774 #endif // CAFFE2_CORE_TENSOR_H_
TypedCopy copy() const
Returns the typed copy function pointer for individual iterms.
Definition: typeid.h:133
size_t itemsize() const
Return the number of bytes each item takes in the tensor.
Definition: tensor.h:538
void ShareExternalPointer(T *src, size_t capacity, Deleter &&d)
Shares the data with an externally managed pointer.
Definition: tensor.h:378
int canonical_axis_index(int axis_index) const
Returns the &#39;canonical&#39; version of a (usually) user-specified axis, allowing for negative indexing (e...
Definition: tensor.h:573
void CopyFrom(const Tensor< SrcContext > &src, ContextForCopy *context)
Copies the data from a source tensor, with a contex provided to carry out the underlying memcpy opera...
Definition: tensor.h:142
int ndim() const
Returns the number of dimensions of the data.
Definition: tensor.h:530
TIndex dim(const int i) const
Returns the i-th dimension of the tensor.
Definition: tensor.h:606
Tensor()
Initializes an empty tensor.
Definition: tensor.h:78
const void * raw_data() const
Returns a const raw void* pointer of the underlying storage.
Definition: tensor.h:421
void ShareExternalPointer(T *src, size_t capacity=0)
Shares the data with an externally managed pointer.
Definition: tensor.h:367
const vector< TIndex > & dims() const
Returns the dimensions of the tensor as a vector.
Definition: tensor.h:552
Tensor(const Tensor< SrcContext > &src)
Creates a tensor from a source tensor, copying over the content.
Definition: tensor.h:112
TIndex size() const
Returns the size (i.e.
Definition: tensor.h:534
T * mutable_data()
Returns a typed pointer of the underlying storage.
Definition: tensor.h:519
const CaffeTypeId & id() const
Returns the type id.
Definition: typeid.h:115
void CopyFrom(const Tensor< SrcContext > &src)
Copies the data from a source tensor.
Definition: tensor.h:166
size_t nbytes() const
Returns the total number of bytes of the storage.
Definition: tensor.h:544
TypeMeta is a thin class that allows us to store the type of a container such as a blob...
Definition: typeid.h:66
const TypeMeta & meta() const
Returns the TypeMeta object associated with the current data type.
Definition: tensor.h:585
void Extend(TIndex num, float growthPct, ContextForCopy *context)
Extends the outer-most dimension of this tensor by num elements, preserving the existing data...
Definition: tensor.h:183
void * raw_mutable_data()
Returns a mutable raw pointer of the underlying storage.
Definition: tensor.h:504
string DebugString() const
A utility function to print the debug string for the tensor.
Definition: tensor.h:317
Tensor(const vector< TIndex > &dims)
Creates a tensor of the given dimension.
Definition: tensor.h:86
Tensor(const vector< TIndex > &dims, const vector< T > &values, Context *context)
Creates a tensor, and fills its contents with the given values.
Definition: tensor.h:120
const T * data() const
Returns a typed pointer of the underlying storage.
Definition: tensor.h:433
const char * name() const
Returns a printable name for the type.
Definition: typeid.h:145
Tensor is the basic class in Caffe2 that stores a contiguous memory with its shape information...
Definition: tensor.h:73
Simple registry implementation in Caffe2 that uses static variables to register object creators durin...
PlacementNew ctor() const
Returns the placement new function pointer for individual items.
Definition: typeid.h:127
void Reshape(const vector< TIndex > &dims)
Resizes the tensor without touching underlying storage.
Definition: tensor.h:290
void ResizeLike(const Tensor< OtherContext > &src_tensor)
Resize the tensor like the source tensor.
Definition: tensor.h:279
TypedDestructor dtor() const
Returns the destructor function pointer for individual items.
Definition: typeid.h:139
const size_t & itemsize() const
Returns the size of the item.
Definition: typeid.h:121
void ShareData(const Tensor &src)
Shares the data with another tensor.
Definition: tensor.h:340
Tensor(const T &value, Context *context)
Creates a scalar tensor, and fills its content with the given value.
Definition: tensor.h:132
void Shrink(TIndex outer_dim)
Shrinks the outer-most dimension to given size, keeping the data.
Definition: tensor.h:236
void * raw_mutable_data(const TypeMeta &meta)
Returns a mutable raw pointer of the underlying storage.
Definition: tensor.h:459
vector< TIndex > ToVectorTIndex(const std::vector< int > &src)
A utility function to convert vector<int> to vector<TIndex>.
Definition: tensor.h:28
Commandline flags support for Caffe2.
void Resize(Ts... dim_source)
Resizes a tensor.
Definition: tensor.h:263
bool IsType() const
Checks if the tensor content is of the given data type.
Definition: tensor.h:581
Tensor(const Tensor< SrcContext > &src, ContextForCopy *context)
Creates a tensor from a source tensor, copying over the content.
Definition: tensor.h:100
int dim32(const int i) const
Returns the i-th dimension of the tensor in int.
Definition: tensor.h:594
TIndex size_from_dim_(int k, vector< TIndex > dims)
Return product of all dimensions starting from K.
Definition: tensor.h:35