一、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