NVcaffe源码阅读——Blob的重新构建

来源:互联网 发布:百度云 知乎 编辑:程序博客网 时间:2024/06/06 09:20

NVcaffe源码阅读——blob的重新构建

nvcaffe由于对数据类型做了很大的拓展,所以作者对blob的构建上做了很大改动。即向下的内存/显存管理提供了tensor类,blob类使用tensor类作为成员变量代替以前的data和diff指针;blob提供与caffe相兼容的api借口,保持功能的一致性,添加部分适应新功能的api;最后使用Tblob类包裹blob类,提供更加灵活的模板定义。

Type

变量类型Type在代码里到处可见,它是作者定义不同数据类型的一个枚举结构,定义在caffe.proto最开头。通过定义可以看到目前nvcaffe所支持的数据类型:

// Math and storage typesenum Type {  DOUBLE = 0;  FLOAT = 1;  FLOAT16 = 2;  INT = 3;  // math not supported  UINT = 4;  // math not supported}

其具体数值大小则是个数据类型的字节长度,定义在type.cpp中。

Syncedmem的修改

syncedmem是在tensor(或老版caffe的blob)之下一层、直接管理内存/显存的操作封装。nvcaffe由于着重改进了多gpu并行性能,因此在这一块做了一些改动,主要包括但不限于:
1. to_string:将运行信息字符串化以帮助debug;
2. 在这里实现了原来在blob.cpp中实现的asum、sumsq、amax等数学操作;
3. valid和invalid函数。SyncedMemory类中有一个布尔成员变量valid_,而valid和invalid函数则是修改这个变量的接口。该变量与Tensor类对不同数据类型的数据进行管理的方式紧密相关;
4. to_cpu和to_gpu在新分配内存/显存时扩展了操作。具体表现为:

  1. to_cpu在新分配内存时多了一步加锁操作;
  2. to_gpu在新分配显存时没有使用简单的cudaMalloc,而是封装了一套既可以使用cudaMalloc也可以使用CachingDeviceAllocator的工具,实现在gpu_memory.hpp/cpp的GPUMemory命名空间中,主要功能集中在try_allocate函数上。 当使用CachingDeviceAllocator时,程序会在分配显存时加锁,且不停刷新各个gpu的显存使用情况。 使用CachingDeviceAllocator的原因应该是其’thread-safe and stream-safe on multiple devices’的特性。 由于代码注释很少,加之没有花很多时间去分析,暂不清楚记录各个gpu的显存使用情况的作用是什么。

Tensor

Tensor类的出现是为了nvcaffe支持多种数据类型。tensor.hpp/cpp中的操作都原属于原版caffe的blob.hpp/cpp,现在把涉及到这个新特性的操作单独拿出来。

Tensor类中的操作可以分为这几类:
1. 数据存取;
2. 数据管理(Reshape: 相同数量不同类型的数据大小不一样;convert: 数据类型之间的相互转换);
3. 数据使用(一些blas操作: 要么调用syncedmem.cpp中实现的,要么根据数据类型调用不同模板的mathfunction(2).cpp中的函数)。

Tensor类数据的取用依赖于这两个成员变量:

    // numerical type stored here at a moment (might change due to conversion)    Type type_;    // array of projections to different types (including current type_)    shared_ptr<vector<shared_ptr<SyncedMemory>>> synced_arrays_;

其中type_按照Type枚举结构的定义只有5个不同的整数值。synced_arrays_是一个SyncedMemory类的指针向量,长度为5,对应nvcaffe支持的5种不同的数据类型。当第一次被赋予数据时,Tensor类会将type_变量设置为对应的数据类型,数据的头指针被放在synced_arrays_[type_]处,且该部分数据对应的SyncedMemory类实例的valid变量变为true。如果调用了convert函数,则将type_设置为转换后的数据类型,并在synced_arrays_[type_]处开辟新的或者抹去已有的数据,使用math_function中的caffe_convert函数进行类型转换和数据复制(对元素进行循环迭代)。

当synced_arrays_存取数据时,只对synced_arrays_[type_]进行操作。

image

其他操作如Reshape等较好理解,在此不再赘述。

Blob

Blob类的变化并不大,主要是为了在api上与以前的caffe保持一致。其变化包括:
1. 以前shared_ptr类型的data_和diff_变成了shared_ptr类型的data_tensor_和diff_tensor_;
2. 一些函数,如FromProto等增加了针对不同数据类型而进行分别处理的语句。

TBlob

TBlob是Blob的子类,主要是对诸如cpu_data(), mutable_gpu_data()等取数据指针的方法进行了重新实现。与Blob提供的方法的区别是,Blob可以通过变换模板数据类型来动态转换实际存储的数据,而TBlob则进行了数据类型一致性的约束,不能随意变换数据类型。举cpu_data的实现作例子即可清楚得到对比:

    // Blob code    template<typename Dtype>    const Dtype* cpu_data() const {        // convert data type if Dtype != type_        convert_data(tp<Dtype>());        return static_cast<const Dtype*>(data_tensor_->synced_mem()->cpu_data());    }    // TBlob code    template<typename T = Dtype>    const T* cpu_data() const {        // raise an Error if T != type_        check_integrity(true, data_type(), tp<T>());            return Blob::cpu_data<T>();    }    static void check_integrity(bool do_data, Type current_type, Type new_type)    {    CHECK_EQ(current_type, new_type)        << "Attempt to change TBlob native " << (do_data ? "data" : "diff")        << " type from " << Type_Name(current_type) << " to " <<  Type_Name(new_type);    }

在使用上,Blob用于网络feature的前向和后向的传递,TBlob用于存储网络使用到的中间变量。