【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  
發佈了319 篇原創文章 · 獲贊 77 · 訪問量 31萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章