caffe 源码学习笔记(1) Blob
迫于生计,开始看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 }
Posts in this Series
- caffe 源码阅读笔记
- [施工中]caffe 源码学习笔记(11) softmax
- caffe 源码学习笔记(11) argmax layer
- caffe 源码学习笔记(10) eltwise layer
- caffe 源码学习笔记(9) reduce layer
- caffe 源码学习笔记(8) loss function
- caffe 源码学习笔记(7) slice layer
- caffe 源码学习笔记(6) reshape layer
- caffe 源码学习笔记(5) 卷积
- caffe 源码学习笔记(4) 激活函数
- caffe 源码学习笔记(3) Net
- caffe 源码学习笔记(2) Layer
- caffe 源码学习笔记(1) Blob