【caffe源码研究】第三章:源码篇(2) :Blob 和 SyncedMemory

一、Blob

在之前的介绍中提到

Blob是:

  • 对处理数据的一层封装,用于在Caffe中通信传递。
  • 也为CPU和GPU间提供同步能力
  • 数学上,是一个N维的C风格的存储数组
  • 总的来说,Caffe使用Blob来交流数据,其是Caffe中标准的数组与统一的内存接口,它是多功能的,在不同的应用场景具有不同的含义,如可以是:batches of images(图像), model parameters(模型参数), and derivatives for optimization(最优化的导数)等。

Blob核心代码

/** 
 * @brief A wrapper around SyncedMemory holders serving as the basic 
 *        computational unit through which Layer%s, Net%s, and Solver%s 
 *        interact. 
 * 
 * TODO(dox): more thorough description. 
 */  
template <typename Dtype>  
class Blob {  
 public:  
  Blob()  
       : data_(), diff_(), count_(0), capacity_(0) {}  

  /// @brief Deprecated; use <code>Blob(const vector<int>& shape)</code>.  
  explicit Blob(const int num, const int channels, const int height,  
      const int width);  
  explicit Blob(const vector<int>& shape);  

  .....  

 protected:  
  shared_ptr<SyncedMemory> data_;  
  shared_ptr<SyncedMemory> diff_;  
  shared_ptr<SyncedMemory> shape_data_;  
  vector<int> shape_;  
  int count_;  
  int capacity_;  

  DISABLE_COPY_AND_ASSIGN(Blob);  
};  // class Blob

注:此处只保留了构造函数与成员变量。

说明:

  • Blob在实现上是对SyncedMemory进行了一层封装。
  • shape_为blob维度。
  • data_为原始数据。
  • diff_为梯度信息。
  • count为该blob的总容量(即数据的size),函数count(x,y)(或count(x))返回某个切片[x,y]([x,end])内容量,本质上就是shape[x]shape[x+1]….*shape[y]的值.

count的代码如下

 inline int count(int start_axis, int end_axis) const {
    int count = 1;
    for (int i = start_axis; i < end_axis; ++i) {
      count *= shape(i);
    }
    return count;
  }

Blob使用SyncedMemory类进行数据存储,数据成员 data_指向实际存储数据的内存或显存块,shape_存储了当前blob的维度信息,diff_这个保存了反向传递时候的梯度信息。在Blob中其实不是只有num,channel,height,width这种四维形式,它是一个不定维度的数据结构,将数据展开存储,而维度单独存在一个vector 类型的shape_变量中,这样每个维度都可以任意变化。

Blob中的关键函数

  inline Dtype data_at(const int n, const int c, const int h,
      const int w) const {
    return cpu_data()[offset(n, c, h, w)];
  }

  inline Dtype diff_at(const int n, const int c, const int h,
      const int w) const {
    return cpu_diff()[offset(n, c, h, w)];
  }

  inline const shared_ptr<SyncedMemory>& data() const {
    CHECK(data_);
    return data_;
  }

  inline const shared_ptr<SyncedMemory>& diff() const {
    CHECK(diff_);
    return diff_;
  }

其中cpu_data()函数会返回数据data_的指针,而offset()函数如下,返回数组的偏移位置。

inline int offset(const int n, const int c = 0, const int h = 0,
      const int w = 0) const {
    return ((n * channels() + c) * height() + h) * width() + w;
  }

data_at这个函数可以读取的存储在此类中的数据,diff_at可以用来读取反向传回来的误差。顺便给个提示,尽量使用data_at(const vector& index)来查找数据。Reshape函数可以修改blob的存储大小,count用来返回存储数据的数量。BlobProto类负责了将Blob数据进行打包序列化到Caffe的模型中。

Blob的shape

由源代码中可以注意到Blob有个成员变量:vector<ini> shape_
其作用:

  • 对于图像数据,shape可以定义为4维的数组(Num, Channels, Height, Width)或(n, k, h, w),所以Blob数据维度为nkhw,Blob是row-major保存的,因此在(n, k, h, w)位置的值物理位置为((n K + k) H + h) W + w。其中Number是数据的batch size,对于256张图片为一个training batch的ImageNet来说n = 256;Channel是特征维度,如RGB图像k = 3
  • 对于全连接网络,使用2D blobs (shape (N, D)),然后调用InnerProductLayer
  • 对于参数,维度根据该层的类型和配置来确定。对于有3个输入96个输出的卷积层,Filter核 11 x 11,则blob为96 x 3 x 11 x 11. 对于全连接层,1000个输出,1024个输入,则blob为1000 x 1024.

二、SyncedMemory

从Blob的定义开始看出来,Blob本质是对SyncedMemory的再封装。

核心代码如下

/** 
 * @brief Manages memory allocation and synchronization between the host (CPU) 
 *        and device (GPU). 
 * 
 * TODO(dox): more thorough description. 
 */

class SyncedMemory {  
 public:  
...  
 const void* cpu_data();  
 const void* gpu_data();  
 void* mutable_cpu_data();  
 void* mutable_gpu_data();  
...  
 private:  
...  
  void* cpu_ptr_;  
  void* gpu_ptr_;  
...  
};  // class SyncedMemory

Blob同时保存了data_diff_,其类型为SyncedMemory的指针。
对于data_(diff_相同),其实际值要么存储在CPU(cpu_ptr_)要么存储在GPU(gpu_ptr_),有两种方式访问CPU数据(GPU相同):

  • 常量方式,void* cpu_data(),其不改变cpu_ptr_指向存储区域的值。
  • 可变方式,void* mutable_cpu_data(),其可改变cpu_ptr_指向存储区域的值。

这与二者的实现方式有关。cpu_data()返回的是const void*的指针,被指向的对象不能修改。

const void* SyncedMemory::cpu_data() {
  to_cpu();
  return (const void*)cpu_ptr_;
}

void* SyncedMemory::mutable_cpu_data() {
  to_cpu();
  head_ = HEAD_AT_CPU;
  return cpu_ptr_;
}

三、再谈Blob

Blob是 Caffe中处理和传递实际数据的封装包,并且在CPU与GPU之间具有同步处理能力。从数学意义上说, blob是按 C风格连续存储的N维数组。

Blobs可根据 CPU主机到GPU 设备的同步需要,屏蔽CPU/GPU混和运算在计上的开销。主机和设备上的内存按需求分配(lazily),以提高内存的使用效率。

在blob中也提供了类似的函数对数据进行读取,也分为常量方式和变量方式。

  const Dtype* cpu_data() const;
  const Dtype* gpu_data() const;
  const Dtype* cpu_diff() const;
  const Dtype* gpu_diff() const;
  Dtype* mutable_cpu_data();
  Dtype* mutable_gpu_data();
  Dtype* mutable_cpu_diff();
  Dtype* mutable_gpu_diff();

之所以这么设计是因为blob使用了一个SyncedMem类来同步CPU和GPU上的数值,以隐藏同步的细节和最小化传送数据。一个经验准则是,如果不想改变数值,就一直使用常量调用,而且绝不要在自定义类中存储指针。每次操作blob时,调用相应的函数来获取它的指针,因为SyncedMem需要用这种方式来确定何时需要复制数据。

实际上,使用GPU时,Caffe中CPU代码先从磁盘中加载数据到blob,同时请求分配一个GPU设备核(device kernel)以使用GPU进行计算,再将计算好的blob数据送入下一层,这样既实现了高效运算,又忽略了底层细节。只要所有layers均有GPU实现,这种情况下所有的中间数据和梯度都会保留在GPU上。

这里有一个示例,用以确定blob何时会复制数据:

// 假定数据在CPU上进行初始化,我们有一个blob 
const Dtype* foo; 
Dtype* bar; 
foo = blob.gpu_data(); // 数据从CPU复制到GPU 
foo = blob.cpu_data(); // 没有数据复制,两者都有最新的内容 
bar = blob.mutable_gpu_data(); // 没有数据复制
// ... 一些操作 ... 
bar = blob.mutable_gpu_data(); // 仍在GPU,没有数据复制 
foo = blob.cpu_data(); // 由于GPU修改了数值,数据从GPU复制到CPU 
foo = blob.gpu_data(); //没有数据复制,两者都有最新的内容 
bar = blob.mutable_cpu_data(); // 依旧没有数据复制 
bar = blob.mutable_gpu_data(); //数据从CPU复制到GPU  
bar = blob.mutable_cpu_data(); //数据从GPU复制到CPU  
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值