跳过正文
  1. Posts/

caffe 源码学习笔记(1) Blob

·3 分钟
目录
99 - 这篇文章属于一个选集。

迫于生计,开始看caffe代码。 会侧重于分析inference部分。

blob 整体介绍
#

blob的含义及目的
#

blob在逻辑上表示的就是所谓的tensor,blob是tensor在caffe中的叫法。 在框架层面上,blob的意义在于对数据进行封装,提供统一的接口。 这里的数据包含训练/inference时用的数据,也包含模型参数,导数等数据。 深度学习离不开在GPU上的计算。 blob对数据的封装使得用户不必关心和cuda有关的数据传输细节。

blob的表示
#

对于图像数据,blob通常为4-dim,也就是N*C*H*W 其中N表示number,也就是batch_num C表示channel H表示height W表示width

在内存中,blob是按照"C-contiguous fashion"存储的,也就是"row-major "

因此,位于(n,c,h,w)的下标在OS中的位置是 ((n * C + c) * H + h) * W + w.

在代码blob.hpp中,我们也可以看到名为offset的函数是其对应的实现。

 1  inline int offset(const int n, const int c = 0, const int h = 0,
 2      const int w = 0) const {
 3    CHECK_GE(n, 0);
 4    CHECK_LE(n, num());
 5    CHECK_GE(channels(), 0);
 6    CHECK_LE(c, channels());
 7    CHECK_GE(height(), 0);
 8    CHECK_LE(h, height());
 9    CHECK_GE(width(), 0);
10    CHECK_LE(w, width());
11    return ((n * channels() + c) * height() + h) * width() + w;
12  }
13  //  给一个indices,计算这是第几个值。
14  inline int offset(const vector<int>& indices) const {
15    CHECK_LE(indices.size(), num_axes());
16    int offset = 0;
17    for (int i = 0; i < num_axes(); ++i) {
18      offset *= shape(i);
19      if (indices.size() > i) {
20        CHECK_GE(indices[i], 0);
21        CHECK_LT(indices[i], shape(i));
22        offset += indices[i];
23      }
24    }
25    return offset;
26  }

需要说明的是,存在一个overload的offset的原因是,对于常见的图像任务,blob是四维的,但是blob也可以是其他数目的维度。

后面我们可以看到,很多函数都有一个overload的版本,一个版本是适用于经典的图像任务,另外一个版本是适用于更一般的任务。

blob 实现细节
#

我们注意到,对于data,diff等数据,blob除了区分了在CPU还是GPU上,还区分了数据是否可以改变:

1const Dtype* cpu_data() const;
2Dtype* mutable_cpu_data();

这样设计的原因是,blob中包含了GPU和CPU上的数据,为了尽可能减少不必要的数据传输,我们可以在确定不会修改数据的情况下使用const版本

同时,区分可变数据和不可变数据也有助于减少bug.

需要注意的是,blob class是禁止copy和assign的。 在C++11及以后,这可以通过将相应的copy control member 设置为"=delete"来实现 而在c++11之前,是通过将这些函数这只为private实现的。

1#define DISABLE_COPY_AND_ASSIGN(classname) \
2private:\
3  classname(const classname&);\
4  classname& operator=(const classname&)

此外,我们可以重点看一下reshape 函数

 1template <typename Dtype>
 2void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
 3    const int width) {
 4  vector<int> shape(4);
 5  shape[0] = num;
 6  shape[1] = channels;
 7  shape[2] = height;
 8  shape[3] = width;
 9  Reshape(shape);
10}
11
12template <typename Dtype>
13void Blob<Dtype>::Reshape(const vector<int>& shape) {
14  CHECK_LE(shape.size(), kMaxBlobAxes);
15  count_ = 1;
16  shape_.resize(shape.size());
17  if (!shape_data_ || shape_data_->size() < shape.size() * sizeof(int)) {
18    shape_data_.reset(new SyncedMemory(shape.size() * sizeof(int)));
19  }
20  int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());
21  for (int i = 0; i < shape.size(); ++i) {
22    CHECK_GE(shape[i], 0);
23    if (count_ != 0) {
24      CHECK_LE(shape[i], INT_MAX / count_) << "blob size exceeds INT_MAX";
25    }
26    count_ *= shape[i];
27    shape_[i] = shape[i];
28    shape_data[i] = shape[i];
29  }
30  if (count_ > capacity_) {
31    capacity_ = count_;
32    data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
33    diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
34  }
35}

和之前的offset该函数相同,仍然是两个版本。 这里值得注意的是,其一是如果有不同接口的多个overload 函数,往往只有一个函数做了真正的工作,而其他函数都是调用该函数来完成。

此外,caffe中使用了shared_ptr来管理数据 当reshape后需要分配新的内存时,

1  if (count_ > capacity_) {
2    capacity_ = count_;
3    data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
4    diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
5  }
99 - 这篇文章属于一个选集。

相关文章

Eigen: C++开源矩阵学习笔记

·2 分钟
接触Eigen的原因是最近在看caffe/caffe2源码,caffe2中使用了Eigen库. Eigen 是一个基于C++模板的线性代数库,直接将库下载后放在项目目录下,然后包含头文件就能使用,非常方便。对于Linux用户,只需要把头文件放到/usr/include 下即可此外,Eigen的接口清晰,稳定高效。

pytorch 函数笔记

·2 分钟
记录一些常用的…总去查文档也是有点麻烦 * tensor.view 的作用是reshape 比如 a = torch.range(1, 16) 得到一个tensor that has 16 elements from 1 to 16. 在a=a.view(4,4)就得到了一个44的tensor。 需要注意reshape之后元素的个数不能改变(16==44) 参数-1的作用是,我懒得算这一维度应该是多少,(由于元素个数不能改变)所以希望自动被计算。**需要注意的是,只有一个维度可以写-1。 **不过view和reshape有些区别:reshape always copies memory. view never copies memory # * torch.squeeze 将输入张量形状中的1 去除并返回。 如果输入是形如(A×1×B×1×C×1×D),那么输出形状就为: (A×B×C×D)当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: (A×1×B), squeeze(input, 0) 将会保持张量不变,只有用 squeeze(input, 1),形状会变成 (A×B)。注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。 # * torch.unsqueeze 返回一个新的张量,对输入的制定位置插入维度 1 注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。如果dim为负,则将会被转化dim+input.dim()+1 # 1>>> x = torch.Tensor([1, 2, 3, 4]) 2>>> torch.unsqueeze(x, 0) 3 1 2 3 4 4[torch.FloatTensor of size 1x4] 5>>> torch.unsqueeze(x, 1) 6 1 7 2 8 3 9 4 10[torch.FloatTensor of size 4x1] 11 12 13 14 * tensor.expand(size) 扩展tensor.可以保持维度数目不变,每一维度的size增加(比如AB变到C*D,其中C>=A,D>=B).-1参数表示某一个维度的size不发生改变 . 有可以扩展tensor到更多的维度,新增加的维度会默认放在最前面,并且不能以-1作为参数。 # * tensor.contiguous 将一个tensor变成连续的。(一些ops如expand/expand_as会让tensor 不连续) #