Keson's blog

Caffe解读4 -- Blob

简介

  • Blob在Caffe中是对于SyncedMemory的封装,Blob是Caffe中保存数据的类,是在各个Layer,Net,Solver之间传递的基本计算单元。Caffe内部数据存储和通讯都是通过Blob来完成,Blob提供统一的存储操作接口,可用来保存训练数据和模型参数等。

  • 在Blob中主要定义了关于数据data_和梯度diff_以及相关的一系列方法,使用的变量都是SyncedMemory的智能指针,所以在解读Blob之前,需要先看上一篇的

    Caffe解读3 – SyncedMemory

模块说明

Blob是一个N维连续数组。批处理图像数据时通常使用4维Blob,Blob的维度可以表示为(N, K, H, W),每个维度的意思分别是:

  • N:数据的个数,例如batch_size的大小
  • K:如果是图像,可以理解为通道数量,网络中间可以理解为feature map的数量
  • H,W: 图像或者滤波器的高度和宽度

Blob中数据是row-major存储的,W是变化最快的维度,例如在(n, k, h, w)处的数据,其物理偏移量计算方式为:

$$
((nK+k)H+h)*W+w
$$

具体实现

1.私有保护变量

1
2
3
4
5
6
7
protected:
shared_ptr<SyncedMemory> data_;
shared_ptr<SyncedMemory> diff_;
shared_ptr<SyncedMemory> shape_data_;
vector<int> shape_;
int count_;
int capacity_;

shared_ptr是智能指针,使用引用计数,当计数为0时,自动释放内存。其中

  • data_ : 用来存放正向传播时的数据
  • diff_ : 用来存放反向传播时的梯度
  • shape_data_: 用来存储Blob的形状数据的SyncedMemor智能指针
  • shape_ : 用来存储Blob的形状数据
  • count_ : 表示Blob中的元素个数,等于 $num \times channels \times height \times width$
  • capacity_ : 表示Blob的容量

构造函数

总共声明和实现了3种构造函数

1
2
3
4
Blob(): data_(), diff_(), count_(0), capacity_(0) {}
explicit Blob(const int num, const int channels, const int height,
const int width);
explicit Blob(const vector<int>& shape);

explict关键字可以防止构造函数的隐式转换,构造函数的实现主要是调用了Reshape函数来完成data_和diff_的共享内存对象SyncedMemory的申请

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename Dtype>
Blob<Dtype>::Blob(const int num, const int channels, const int height,
const int width)
// capacity_ must be initialized before calling Reshape
: capacity_(0) {
Reshape(num, channels, height, width);
}
template <typename Dtype>
Blob<Dtype>::Blob(const vector<int>& shape)
// capacity_ must be initialized before calling Reshape
: capacity_(0) {
Reshape(shape);
}

Reshape函数

Reshape能够被调用用来实现 (1)内存的初始化分配;(2)在Layer::Reshape或者Layer::Forward时 用来调节top blob的维度。 当改变blob的size时,只有当原来的内存已经不够了才会重新分配,而创建后多余的内存是不会释放的。需要注意的是,当对输入blob进行reshape时,不能马上调用Net::Backward,因为需要在进行
reshape后需要调用Net::Forward或者Net::Reshape将新的输入shape传递到更高的层。

Reshape成员函数有4种:

  • void Reshape(const int num, const int channels, const int height,const int width);
  • void Reshape(const vector<int>& shape);
  • void Reshape(const BlobShape& shape);
  • void ReshapeLike(const Blob& other);

Reshape成员函数1

1
2
3
4
5
6
7
8
9
10
11
//直接用num,channels,height,width来完成reshape,调用了Reshape(const vector<int> &shape)
template <typename Dtype>
void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
const int width) {
vector<int> shape(4);
shape[0] = num;
shape[1] = channels;
shape[2] = height;
shape[3] = width;
Reshape(shape); //调用了类型2的Reshape函数
}

Reshape成员函数2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template <typename Dtype>
void Blob<Dtype>::Reshape(const vector<int>& shape) {
/*
* CHECK_LE 位于logging.h文件 767行
* 定义为:
* #define CHECK_LE(val1, val2) CHECK_OP(_LE, <=, val1, val2)
* 用来检查val1<=val2, 用到了GLOG日志库
*/
CHECK_LE(shape.size(), kMaxBlobAxes); //kMaxBlobAxes定义为shape参数最大的个数,设定为32
count_ = 1; //开始初始化时赋值为1,因为后面要乘以shape_中的每个元素值
shape_.resize(shape.size());//shape_ 开始初始化
//shape_data_的初始化和赋值,它是一个SyncedMemory类指针
if (!shape_data_ || shape_data_->size() < shape.size() * sizeof(int)) {
shape_data_.reset(new SyncedMemory(shape.size() * sizeof(int)));
}
int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());//获得shape_data_的cpu内存地址
for (int i = 0; i < shape.size(); ++i) {
CHECK_GE(shape[i], 0); //检查shape中该参数是否为0
if (count_ != 0) {
//检查乘以shape[i]后,count_是否会超过INT_MAX
CHECK_LE(shape[i], INT_MAX / count_) << "blob size exceeds INT_MAX";
}
count_ *= shape[i]; //统计Blob元素个数= num*channels*height*width
shape_[i] = shape[i]; //给成员变量shape_ 赋值
shape_data[i] = shape[i]; //给shape_data_赋值
}
//超过容量,设定容量为count_
if (count_ > capacity_) {
capacity_ = count_;
data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));//data_ 进行内存分配
diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));//diff_ 进行内存分配
}
}

Reshape成员函数3

Reshape(const BlobShape& shape)BlobShape来进行Reshape, BlobShape是在caffe.proto中定义的,用来定义Blob的shape维度的。

1
2
3
4
// Specifies the shape (dimensions) of a Blob.
message BlobShape {
repeated int64 dim = 1 [packed = true];
}
1
2
3
4
5
6
7
8
9
template <typename Dtype>
void Blob<Dtype>::Reshape(const BlobShape& shape) {
CHECK_LE(shape.dim_size(), kMaxBlobAxes);
vector<int> shape_vec(shape.dim_size());
for (int i = 0; i < shape.dim_size(); ++i) {
shape_vec[i] = shape.dim(i);
}
Reshape(shape_vec); //同样调用了Reshape函数2
}

Reshape成员函数4

ReshapeLike(const Blob<Dtype>& other),用其他的Blob参数进行Reshape

1
2
3
4
template <typename Dtype>
void Blob<Dtype>::ReshapeLike(const Blob<Dtype>& other) {
Reshape(other.shape()); //同样调用了Reshape函数2
}

shape 数据输出函数

两个内联函数用来输出shape的形状数据

1
2
3
4
5
6
7
8
9
inline string shape_string() const {
ostringstream stream;
for (int i = 0; i < shape_.size(); ++i) {
stream << shape_[i] << " ";
}
stream << "(" << count_ << ")";
return stream.str();
}
inline const vector<int>& shape() const { return shape_; }

一些变量访问函数

  • num_axes(): 返回shape_ 的size
  • count():返回count_
  • CanonicalAxisIndex(int axis_index):返回规范化的坐标,支持负坐标的访问
  • shape(int index):返回shape_索引处的值
  • LegacyShape(int index):内部调用shape(intdex),多了一些合法性检查
  • num(),channels(),height(),width():分别返回对应的值

num_axes()

1
inline int num_axes() const { return shape_.size(); }

count()

3种形式的count()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//直接返回count_
inline int count() const { return count_; }
//计算一个片内的元素个数
inline int count(int start_axis, int end_axis) const {
CHECK_LE(start_axis, end_axis);
CHECK_GE(start_axis, 0);
CHECK_GE(end_axis, 0);
CHECK_LE(start_axis, num_axes());
CHECK_LE(end_axis, num_axes());
int count = 1;
for (int i = start_axis; i < end_axis; ++i) {
count *= shape(i);
}
return count;
}
//给定一个起始,计算到最后片的元素个数
inline int count(int start_axis) const {
return count(start_axis, num_axes());
}

CanonicalAxisIndex()

用来进行坐标的规范化,和Python一样,支持负数的访问。

1
2
3
4
5
6
7
8
9
10
11
12
inline int CanonicalAxisIndex(int axis_index) const {
CHECK_GE(axis_index, -num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
CHECK_LT(axis_index, num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
if (axis_index < 0) {
return axis_index + num_axes();
}
return axis_index;
}

shape(int index)

返回索引处shape的值

1
2
3
inline int shape(int index) const {
return shape_[CanonicalAxisIndex(index)];
}

LegacyShape(int index)

这个是只针对4维Blob的shape数据访问版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
inline int LegacyShape(int index) const {
CHECK_LE(num_axes(), 4)
<< "Cannot use legacy accessors on Blobs with > 4 axes.";
CHECK_LT(index, 4);
CHECK_GE(index, -4);
if (index >= num_axes() || index < -num_axes()) {
// Axis is out of range, but still in [0, 3] (or [-4, -1] for reverse
// indexing) -- this special case simulates the one-padding used to fill
// extraneous axes of legacy blobs.
return 1;
}
return shape(index);
}

其他变量访问函数

1
2
3
4
5
6
7
8
/// @brief Deprecated legacy shape accessor num: use shape(0) instead.
inline int num() const { return LegacyShape(0); }
/// @brief Deprecated legacy shape accessor channels: use shape(1) instead.
inline int channels() const { return LegacyShape(1); }
/// @brief Deprecated legacy shape accessor height: use shape(2) instead.
inline int height() const { return LegacyShape(2); }
/// @brief Deprecated legacy shape accessor width: use shape(3) instead.
inline int width() const { return LegacyShape(3); }

CPU和GPU中数据的获得

以下是一些get和set函数

  • const Dtype* cpu_data() const;
  • void set_cpu_data(Dtype* data);
  • const int* gpu_shape() 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();

cpu_data()

返回数据data_在cpu内存中的地址指针

1
2
3
4
5
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_data() const {
CHECK(data_);
return (const Dtype*)data_->cpu_data();
}

set_cpu_data()

设定data_在cpu中的数据,直接用指针替换的方式

1
2
3
4
5
template <typename Dtype>
void Blob<Dtype>::set_cpu_data(Dtype* data) {
CHECK(data);
data_->set_cpu_data(data); //在Syscedmem.cpp中实现,用cpu_ptr_ = data实现替换
}

gpu_shape

返回的在gpu中存储的shape_data_,即gpu中存储的shape形状变量

1
2
3
4
5
template <typename Dtype>
const int* Blob<Dtype>::gpu_shape() const {
CHECK(shape_data_);
return (const int*)shape_data_->gpu_data();
}

gpu_data()

返回数据data_在gpu内存中的地址指针

1
2
3
4
5
template <typename Dtype>
const Dtype* Blob<Dtype>::gpu_data() const {
CHECK(data_);
return (const Dtype*)data_->gpu_data();
}

cpu_diff()

返回梯度diff_在cpu内存中的地址指针

1
2
3
4
5
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_diff() const {
CHECK(diff_);
return (const Dtype*)diff_->cpu_data();
}

gpu_diff()

返回梯度diff_在gpu内存中的地址指针

1
2
3
4
5
template <typename Dtype>
const Dtype* Blob<Dtype>::gpu_diff() const {
CHECK(diff_);
return (const Dtype*)diff_->gpu_data();
}

mutable版本

下面这4个函数与上面类似,不同之处在于mutable,可以对其进行修改,而上面的函数返回形式是const,不可修改的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_cpu_data() {
CHECK(data_);
return static_cast<Dtype*>(data_->mutable_cpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_gpu_data() {
CHECK(data_);
return static_cast<Dtype*>(data_->mutable_gpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_cpu_diff() {
CHECK(diff_);
return static_cast<Dtype*>(diff_->mutable_cpu_data());
}
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_gpu_diff() {
CHECK(diff_);
return static_cast<Dtype*>(diff_->mutable_gpu_data());
}

具体offset位置处的访问

先需要用offset()函数计算具体的位置index,然后对data_diff_具体index处进行访问,主要的函数有

  • offset(const int n, const int c = 0, const int h = 0, const int w = 0)
  • offset(const vector<int>& indices)
  • data_at(const int n, const int c, const int h, const int w)
  • diff_at(const int n, const int c, const int h, const int w)
  • data_at(const vector<int>& index)
  • diff_at(const vector<int>& index)

offset()

offset()函数有两个实现的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
inline int offset(const int n, const int c = 0, const int h = 0,
const int w = 0) const {
CHECK_GE(n, 0);
CHECK_LE(n, num());
CHECK_GE(channels(), 0);
CHECK_LE(c, channels());
CHECK_GE(height(), 0);
CHECK_LE(h, height());
CHECK_GE(width(), 0);
CHECK_LE(w, width());
return ((n * channels() + c) * height() + h) * width() + w;
}
//用[n,c,h,w]的vector向量实现
inline int offset(const vector<int>& indices) const {
CHECK_LE(indices.size(), num_axes());
int offset = 0;
for (int i = 0; i < num_axes(); ++i) {
offset *= shape(i);
if (indices.size() > i) {
CHECK_GE(indices[i], 0);
CHECK_LT(indices[i], shape(i));
offset += indices[i];
}
}
return offset;
}

data_at()diff_data()函数的访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 Dtype data_at(const vector<int>& index) const {
return cpu_data()[offset(index)];
}
inline Dtype diff_at(const vector<int>& index) const {
return cpu_diff()[offset(index)];
}

data_diff_指针

1
2
3
4
5
6
7
8
9
inline const shared_ptr<SyncedMemory>& data() const {
CHECK(data_);
return data_;
}
inline const shared_ptr<SyncedMemory>& diff() const {
CHECK(diff_);
return diff_;
}

data_的更新

对于data_的更新,一般是对其减去反向传播的diff_乘以相应的系数,这里主要用到的函数有

  • void Update(): 用来对data_进行更新
  • Dtype asum_data() const: 对data_求绝对值之和,即L1范数
  • Dtype asum_diff() const: 对diff_求L1范数
  • Dtype sumsq_data() const:对data_求平方和之和,即L2范数
  • Dtype sumsq_diff() const:对diff_求L2范数
  • void scale_data(Dtype scale_factor):对data_乘以相应的标量
  • void scale_diff(Dtype scale_factor):对diff_乘以相应的标量

Update()方法

Updata()方法组要是用来对data_进行diff_的更新,主要是封装了cblas和cublas中的版本,其中里面分别有针对floatdouble版本的,因此没有实现int版本和unsigned int版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <> void Blob<unsigned int>::Update() { NOT_IMPLEMENTED; }
template <> void Blob<int>::Update() { NOT_IMPLEMENTED; }
template <typename Dtype>
void Blob<Dtype>::Update() {
// We will perform update based on where the data is located.
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU: //对于cpu中的数据,调用caffe_axpy()
// perform computation on CPU
caffe_axpy<Dtype>(count_, Dtype(-1),
static_cast<const Dtype*>(diff_->cpu_data()),
static_cast<Dtype*>(data_->mutable_cpu_data()));
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
// perform computation on GPU //对于gpu中的数据调用caffe_gpu_axpy()
caffe_gpu_axpy<Dtype>(count_, Dtype(-1),
static_cast<const Dtype*>(diff_->gpu_data()),
static_cast<Dtype*>(data_->mutable_gpu_data()));
#else
NO_GPU; //log报错
#endif
break;
default:
LOG(FATAL) << "Syncedmem not initialized.";
}
}

Update函数中调用了caffe_axpy,分别封装了cpu实现版本cblas_saxpy,主要是调用了cblas中的函数;
gpu实现版本caffe_gpu_axpy,主要是调用了cublas中的函数。

两者的声明在/caffe/util/math_function.hpp中:

1
2
3
4
5
6
7
template <typename Dtype>
void caffe_axpy(const int N, const Dtype alpha, const Dtype* X,
Dtype* Y);
template <typename Dtype>
void caffe_gpu_axpy(const int N, const Dtype alpha, const Dtype* X,
Dtype* Y);

cpu版本的实现只有一种,位于/caffe/util/math_function.cpp中

1
2
3
template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,
float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); }

cblas_saxpy函数中,N是这个向量的元素个数,在Blob中就是count_。alpha是X前面的系数,X,Y 是输入的矢量。其中的1和1分别是X,Y的步进,这里每个元素都要更新,所以是1。函数实现的功能是

\begin{equation}
Y=alpha * X+Y
\end{equation}

gpu版本的实现有两种,位于/caffe/util/math_function.cu中:

1
2
3
4
5
6
7
8
9
10
11
template <>
void caffe_gpu_axpy<float>(const int N, const float alpha, const float* X,
float* Y) {
CUBLAS_CHECK(cublasSaxpy(Caffe::cublas_handle(), N, &alpha, X, 1, Y, 1));
}
template <>
void caffe_gpu_axpy<double>(const int N, const double alpha, const double* X,
double* Y) {
CUBLAS_CHECK(cublasDaxpy(Caffe::cublas_handle(), N, &alpha, X, 1, Y, 1));
}

求L1范数

asum_data()函数用来计算data数据的绝对值之和(L1范数),asum_diff()用来计算梯度数据diff的L1范数。
主要是调用了cpu版本的caffe_cpu_asum和gpu版本的caffe_gpu_asum,这两个函数同样分别对cblas和cublas中的函数进行了封装。

asum_data()的实现如下,asum_diff()的实现类似,无非是将data_指针换成了diff_指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template <> unsigned int Blob<unsigned int>::asum_data() const {
NOT_IMPLEMENTED;
return 0;
}
template <> int Blob<int>::asum_data() const {
NOT_IMPLEMENTED;
return 0;
}
template <typename Dtype>
Dtype Blob<Dtype>::asum_data() const {
if (!data_) { return 0; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
return caffe_cpu_asum(count_, cpu_data());
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
{
Dtype asum;
caffe_gpu_asum(count_, gpu_data(), &asum);
return asum;
}
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return 0;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return 0;
}

其中,caffe_cpu_asumcaffe_gpu_asum的声明如下,位于/caffe/util/math_function.hpp

1
2
3
4
5
template <typename Dtype>
Dtype caffe_cpu_asum(const int n, const Dtype* x);
template <typename Dtype>
void caffe_gpu_asum(const int n, const Dtype* x, Dtype* y);

caffe_cpu_asum的实现在/caffe/util/math_function.cpp中,针对floatdouble有两个版本

1
2
3
4
5
6
7
8
9
template <>
float caffe_cpu_asum<float>(const int n, const float* x) {
return cblas_sasum(n, x, 1);//用来计算向量x的和,共有n个元素,1是stride,每个元素都用所以取1
}
template <>
double caffe_cpu_asum<double>(const int n, const double* x) {
return cblas_dasum(n, x, 1);
}

caffe_gpu_asum的实现在/caffe/util/math_function.cu中,针对floatdouble有两个版本

1
2
3
4
5
6
7
8
9
10
//gpu的计算使用cublas
template <>
void caffe_gpu_asum<float>(const int n, const float* x, float* y) {
CUBLAS_CHECK(cublasSasum(Caffe::cublas_handle(), n, x, 1, y));
}
template <>
void caffe_gpu_asum<double>(const int n, const double* x, double* y) {
CUBLAS_CHECK(cublasDasum(Caffe::cublas_handle(), n, x, 1, y));
}

L2范数

sumq_data()函数用来计算data数据的平方和(L2范数),sumsq_diff() 用来计算梯度数据diff的L2范数。
主要是调用了cpu版本的caffe_cpu_dout和gpu版本的caffe_gpu_dot函数。

sumq_data()的实现如下,sumsq_diff()与之类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
template <> unsigned int Blob<unsigned int>::sumsq_data() const {
NOT_IMPLEMENTED;
return 0;
}
template <> int Blob<int>::sumsq_data() const {
NOT_IMPLEMENTED;
return 0;
}
template <typename Dtype>
Dtype Blob<Dtype>::sumsq_data() const {
Dtype sumsq;
const Dtype* data;
if (!data_) { return 0; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
data = cpu_data();
sumsq = caffe_cpu_dot(count_, data, data); //cpu版本
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
data = gpu_data();
caffe_gpu_dot(count_, data, data, &sumsq); //gpu版本
#else
NO_GPU;
#endif
break;
case SyncedMemory::UNINITIALIZED:
return 0;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return sumsq;
}

其中caffe_cpu_doutcaffe_gpu_dot的声明如下:

1
2
3
4
5
template <typename Dtype>
Dtype caffe_cpu_dot(const int n, const Dtype* x, const Dtype* y);
template <typename Dtype>
void caffe_gpu_dot(const int n, const Dtype* x, const Dtype* y, Dtype* out);

caffe_cpu_dot的实现调用了

1
2
3
4
template <typename Dtype>
Dtype caffe_cpu_dot(const int n, const Dtype* x, const Dtype* y) {
return caffe_cpu_strided_dot(n, x, 1, y, 1);
}

caffe_cpu_strided_dot是一个模板函数,对cblas_sdotcblas_ddot进行了封装,其声明和实现分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename Dtype>
Dtype caffe_cpu_strided_dot(const int n, const Dtype* x, const int incx,
const Dtype* y, const int incy);
template <>
float caffe_cpu_strided_dot<float>(const int n, const float* x, const int incx,
const float* y, const int incy) {
return cblas_sdot(n, x, incx, y, incy);
}
template <>
double caffe_cpu_strided_dot<double>(const int n, const double* x,
const int incx, const double* y, const int incy) {
return cblas_ddot(n, x, incx, y, incy);
}

caffe_gpu_dot是一个模板函数,它的声明如下:

1
2
template <typename Dtype>
void caffe_gpu_dot(const int n, const Dtype* x, const Dtype* y, Dtype* out);

该函数有floatdouble两个实现版本,位于math_functions.cu中,实现如下:

1
2
3
4
5
6
7
8
9
10
11
template <>
void caffe_gpu_dot<float>(const int n, const float* x, const float* y,
float* out) {
CUBLAS_CHECK(cublasSdot(Caffe::cublas_handle(), n, x, 1, y, 1, out));
}
template <>
void caffe_gpu_dot<double>(const int n, const double* x, const double* y,
double * out) {
CUBLAS_CHECK(cublasDdot(Caffe::cublas_handle(), n, x, 1, y, 1, out));
}

scale_datascale_diff

scale_data函数和scale_diff函数主要是对Blob内的data_向量或者diff_向量乘以一个标量。
主要调用的两个函数cpu版本的caffe_scal()和gpu版本的caffe_gpu_scal()

scale_data的实现如下,scale_diff与之类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template <> void Blob<unsigned int>::scale_data(unsigned int scale_factor) {
NOT_IMPLEMENTED;
}
template <> void Blob<int>::scale_data(int scale_factor) {
NOT_IMPLEMENTED;
}
template <typename Dtype>
void Blob<Dtype>::scale_data(Dtype scale_factor) {
Dtype* data;
if (!data_) { return; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
data = mutable_cpu_data();
caffe_scal(count_, scale_factor, data); //cpu版本
return;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
data = mutable_gpu_data();
caffe_gpu_scal(count_, scale_factor, data); //gpu版本
return;
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
}

caffe_scal的模板函数声明为

1
2
template <typename Dtype>
void caffe_scal(const int N, const Dtype alpha, Dtype *X);

对应的floatdouble实现版本如下,分别是对cblas_sscalcblas_dscal函数的调用。

1
2
3
4
5
6
7
8
9
template <>
void caffe_scal<float>(const int N, const float alpha, float *X) {
cblas_sscal(N, alpha, X, 1);
}
template <>
void caffe_scal<double>(const int N, const double alpha, double *X) {
cblas_dscal(N, alpha, X, 1);
}

caffe_gpu_scal()的声明如下:

1
2
template <typename Dtype>
void caffe_gpu_scal(const int N, const Dtype alpha, Dtype *X);

caffe_gpu_scal()的实现同样有两个版本:

1
2
3
4
5
6
7
8
9
10
template <>
void caffe_gpu_scal<float>(const int N, const float alpha, float *X) {
CUBLAS_CHECK(cublasSscal(Caffe::cublas_handle(), N, &alpha, X, 1));
}
template <>
void caffe_gpu_scal<double>(const int N, const double alpha, double *X) {
CUBLAS_CHECK(cublasDscal(Caffe::cublas_handle(), N, &alpha, X, 1));
}

其他的一些函数

  • ShareData
  • ShareDiff
  • ShapeEquals
  • CopyFrom
  • FromProto
  • ToProto

ShareDataShareDiff

ShareDataShareDiff实现方式是直接将data_diff_的指针替换成其他类中的指针。这可以简化Layer中前向传递时只是简单的copy的情况。

1
2
3
4
5
6
7
8
9
10
11
template <typename Dtype>
void Blob<Dtype>::ShareData(const Blob& other) {
CHECK_EQ(count_, other.count());
data_ = other.data();
}
template <typename Dtype>
void Blob<Dtype>::ShareDiff(const Blob& other) {
CHECK_EQ(count_, other.count());
diff_ = other.diff();
}

ShapeEquals

Blob的shape是否相同的检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename Dtype>
bool Blob<Dtype>::ShapeEquals(const BlobProto& other) {
if (other.has_num() || other.has_channels() ||
other.has_height() || other.has_width()) {
// Using deprecated 4D Blob dimensions --
// shape is (num, channels, height, width).
// Note: we do not use the normal Blob::num(), Blob::channels(), etc.
// methods as these index from the beginning of the blob shape, where legacy
// parameter blobs were indexed from the end of the blob shape (e.g., bias
// Blob shape (1 x 1 x 1 x N), IP layer weight Blob shape (1 x 1 x M x N)).
return shape_.size() <= 4 &&
LegacyShape(-4) == other.num() &&
LegacyShape(-3) == other.channels() &&
LegacyShape(-2) == other.height() &&
LegacyShape(-1) == other.width();
}
vector<int> other_shape(other.shape().dim_size());
for (int i = 0; i < other.shape().dim_size(); ++i) {
other_shape[i] = other.shape().dim(i);
}
return shape_ == other_shape;
}

CopyFrom

CopyFrom的声明如下,其中copy_difffalse,则拷贝的是data,否则拷贝diff

1
2
void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,
bool reshape = false);

实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
template <typename Dtype>
void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff, bool reshape) {
//先做size检查
if (source.count() != count_ || source.shape() != shape_) {
if (reshape) {
ReshapeLike(source);
} else {
LOG(FATAL) << "Trying to copy blobs of different sizes.";
}
}
switch (Caffe::mode()) {
case Caffe::GPU:
if (copy_diff) {
caffe_copy(count_, source.gpu_diff(),
static_cast<Dtype*>(diff_->mutable_gpu_data()));
} else {
caffe_copy(count_, source.gpu_data(),
static_cast<Dtype*>(data_->mutable_gpu_data()));
}
break;
case Caffe::CPU:
if (copy_diff) {
caffe_copy(count_, source.cpu_diff(),
static_cast<Dtype*>(diff_->mutable_cpu_data()));
} else {
caffe_copy(count_, source.cpu_data(),
static_cast<Dtype*>(data_->mutable_cpu_data()));
}
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
}

其中,caffe_copy封装了cpu内存之间的内存copy和gpu内存的copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename Dtype>
void caffe_copy(const int N, const Dtype* X, Dtype* Y) {
if (X != Y) {
if (Caffe::mode() == Caffe::GPU) {
#ifndef CPU_ONLY
// NOLINT_NEXT_LINE(caffe/alt_fn)
CUDA_CHECK(cudaMemcpy(Y, X, sizeof(Dtype) * N, cudaMemcpyDefault));
#else
NO_GPU;
#endif
} else {
memcpy(Y, X, sizeof(Dtype) * N); // NOLINT(caffe/alt_fn)
}
}
}

FromProtoToProto

FromProto用proto文件来实现Blob的初始化,ToProto是将Blob的内容写入到proto文件

总结

总而言之,Blob相对而言是一个比较简单的类,在看懂SyncedMemory以后,就比较容易看懂这个类了,里面的各种调用数学函数的声明都是在/caffe/util/math_functions.hpp中,实现分别是在math_functions.cpp和math_functions.cu中。

在类的最后可以看到 DISABLE_COPY_AND_ASSIGN(Blob),这是一个宏函数,可以看到它的实现,主要是禁止这个类的拷贝和赋值操作,是为了防止两个大型数据结构内容的复制和赋值。如果要使用,应该是使用指针和引用来指向。

1
2
3
4
5
// Disable the copy and assignment operator for a class.
#define DISABLE_COPY_AND_ASSIGN(classname) \
private:\
classname(const classname&);\
classname& operator=(const classname&)