深鉴科技DNNC编译器浅析

从官网下载了他们产品试用版,目前手头没有合适的开发板,没法运行实际的例子,但是作为一个程序猿,我比较感兴趣的是其DNNC编译器的实现。看其文档,其可以让用户轻松完成从Caffe框架到DPU的转换。本文基于其软件接口,进行了简单的分析总结,仅供参考。

分析软件包中 /host_x86/pkgs/bin/dnnc文件,可以得到如下的一些发现:

1、集成了Caffe中proto并进行了扩展,用于读取”.prototxt”和”.caffemodel”文件,还增加了如下的参数:

caffe::FixedParameter,猜测这个新增的参数和定点化有关。标准的Caffe里未找到这个参数,可能是深鉴或者其他开发者做了扩展。
主要成员如下,包含了定点化位宽、定点方法等标志位。

caffe::FixedParameter
caffe::FixedParameter::FixedMethod_MAX
caffe::FixedParameter::FixedMethod_MIN
caffe::FixedParameter::kFixInfoFieldNumber
caffe::FixedParameter::kBitWidthFieldNumber
caffe::FixedParameter::DIFF_A
caffe::FixedParameter::DIFF_S
caffe::FixedParameter::OVER_FLOW

caffe::TilingParameter, 猜测和数据划分有关,由于查看不到有用信息,这个不列举了。

2、DNNC中会把神经网络模型编译成内部计算图IR,附加控制流和数据流。LayerGraph、SuperGraph等数据结构在其中扮演了重要的角色,其主要接口有:

忽略函数的返回值:

dnnc::LayerGraph
dnnc::LayerGraph::CreateLayer(dnnc::LayerType, const std::string&)
dnnc::LayerGraph::CreateLayer(dnnc::LayerType, const std::string&, const dnnc::parameter_t&)
dnnc::LayerGraph::CreateLayer(dnnc::Layer*)
dnnc::LayerGraph::RemoveLayer(dnnc::Layer*)
dnnc::LayerGraph::CopyLayer(const dnnc::Layer*)
dnnc::LayerGraph::AddControlLayer()
dnnc::LayerGraph::AddEdge(dnnc::Layer*, int, dnnc::Layer*, int)
dnnc::LayerGraph::RemoveEdge(const dnnc::LayerEdge*)
dnnc::LayerGraph::CreateSubgraph(std::set< const dnnc::Layer* >)

LayerGraph图上的每个结点即为Layer,主要接口如下:

dnnc::Layer
dnnc::Layer::input(int) const
dnnc::Layer::output(int) const
dnnc::Layer::in_edge(int) const
dnnc::Layer::in_edges() const
dnnc::Layer::out_edge(int) const
dnnc::Layer::out_edges() const

此外数个Layer还会合并成一个Super结构,组成SuperGraph。

dnnc::SuperGraph
dnnc::SuperGraph::CreateSuper(const std::string&, dnnc::SuperType)
dnnc::SuperGraph::RemoveSuper(dnnc::Super*)
dnnc::SuperGraph::AddEdge(dnnc::Super*, dnnc::Layer*, dnnc::Super*, dnnc::Layer*)
dnnc::SuperGraph::RemoveEdge(const dnnc::SuperEdge*)
dnnc::Super
dnnc::Super::in_degree(dnnc::Layer const*) const
dnnc::Super::in_layers() const
dnnc::Super::out_degree(dnnc::Layer const*) const
dnnc::Super::out_layers() const
dnnc::Super::successors() const
dnnc::Super::predecessors() const
dnnc::Super::max_memory_addr() const
dnnc::Super::workload() const
3、图的划分,Partitioner类在LayerGraph上进行划分。
dnnc::Partitioner
dnnc::Partitioner::Partition(std::shared_ptr< dnnc::LayerGraph >&)
dnnc::Partitioner::is_partitional(dnnc::LayerGraph const*, dnnc::Layer const*, std::set< dnnc::Layer const* >&, std::set< dnnc::Layer const* >&) const
dnnc::Partitioner::check_support_info(dnnc::Layer const*) const
4、在图上会进行拓扑排序

采用了一个模板函数来进行拓扑排序

拓扑排序
void dnnc::GetTopologicalSort< dnnc::LayerGraph, dnnc::Layer >(dnnc::LayerGraph const*, std::vector< dnnc::Layer* >&)
5、依次遍历并执行某些操作,具体封装在了 LayerPassManager中:
dnnc::LayerPassManager
dnnc::LayerPassManager::Registry(dnnc::PassEntry&)
dnnc::LayerPassManager::Excute(dnnc::LayerGraph const*, std::string const&)
6、针对卷积层和全连接层貌似还有额外的操作,封装在ConvFcSGraphBuilder中:
dnnc::ConvFcSGraphBuilder
dnnc::ConvFcSGraphBuilder::ConvFcSGraphBuilder()
dnnc::ConvFcSGraphBuilder::Build(dnnc::LayerGraph*)
dnnc::ConvFcSGraphBuilder::build(dnnc::SuperGraph*, std::map< int, dnnc::Super* >&, dnnc::Layer*)
dnnc::ConvFcSGraphBuilder::Check(dnnc::LayerGraph*)
dnnc::ConvFcSGraphBuilder::Transform(dnnc::LayerGraph*)
7、关于定点信息,用FixInfo类来进行处理,DNNC会读取DECENT工具生成的定点信息(以前版本定点信息存储来“fix_info.txt”文件中,现在则是封装在了Caffe模型的FixedParameter中)。

具体函相关数应该是

定点化相关函数
dnnc::CaffeModel::fixinfo(std::string const&) const
dnnc::CaffeModel::parsing_fixinfo_from_binary(caffe::NetParameter const&, caffe::NetParameter const&)
dnnc::FixInfo::parsing(std::string const&, std::vector< signed char > const&)
8、内存分配

主要是MemAlloc类

dnnc::MemAlloc
dnnc::MemAlloc::MemAlloc(dnnc::SuperGraph const*)
dnnc::MemAlloc::init()
dnnc::MemAlloc::Run()
dnnc::MemAlloc::allocate_io(std::vector< dnnc::Super* >&)
dnnc::MemAlloc::allocate_io_concat(std::vector< dnnc::Super* >&)
dnnc::MemAlloc::allocate_io_normal(std::vector< dnnc::Super* >&)
dnnc::MemAlloc::allocate_weights_bias(std::vector< dnnc::Super* >&)
dnnc::MemAlloc::collect_concat_offsets(dnnc::Super const*, std::unordered_map< int, unsigned long >&)
dnnc::MemAlloc::init_allocation_status(dnnc::Super const*, std::unordered_map< int, unsigned long >&)
dnnc::MemAlloc::update_allocation_status(dnnc::Layer const*, dnnc::Allocator*, std::unordered_map< int, unsigned long >&)
dnnc::MemAlloc::set_addr(dnnc::Layer const*, long, bool, int)
9、关于代码生成模块,集成了elf、yasm和nasm项目,猜测DNNC利用这些工具来生成汇编和elf文件。

DNNC内部封装了两种指令生成的类: dnnc::ASMGen和dnnc::CodeGen

dnnc::CodeGen
dnnc::CodeGen::Generate(dnnc::Super const*, int)
dnnc::CodeGen::CodeGen(dnnc::DPUType)
dnnc::ASMGen
dnnc::ASMGen::ASMGen(dnnc::DPUType)
dnnc::ASMGen::Generate(std::string const&, std::string const&, dnnc::SuperGraph const*)
dnnc::ASMGen::generate_weights(std::string const&, std::vector< dnnc::Super const* > const&)
dnnc::ASMGen::generate_bias(std::string const&, std::vector< dnnc::Super const* > const&)
dnnc::ASMGen::generate_code(std::string const&, std::vector< dnnc::Super const* > const&, dnnc::CodeGen*)
dnnc::ASMGen::generate_meta(std::string const&, std::vector< dnnc::Super const* > const&, dnnc::Super const*, std::shared_ptr< dnnc::Section > const&)
dnnc::ASMGen::generate_strtab(std::string const&, std::vector< dnnc::Super const* > const&)
dnnc::ASMGen::generate_tensor_descriptor(std::string const&, std::vector< dnnc::Super const* > const&)
dnnc::ASMGen::add_virtual_output(std::shared_ptr< dnnc::Section >&, std::shared_ptr< dnnc::Section >&, std::vector< dnnc::Super const* > const&)
dnnc::ASMGen::get_virtual_outputs(dnnc::Super const*, std::vector< dnnc::Super const* >&)
dnnc::ASMGen::write_file(std::vector< std::shared_ptr< dnnc::Section> >&, std::string const&, std::ofstream&)

猜测dnnc::ASMGen应该比dnnc::CodeGen更加底层一些,然后最底层就是elf、yasm和nasm项目的一些函数

10、输出到文件

从每个Section调用Write函数

dnnc::WeightsSection::Write(std::ostream&) const
dnnc::BiasSection::Write(std::ostream&) const
dnnc::CodeSection::Write(std::ostream&) const
dnnc::MetaDataSection::Write(std::ostream&) const
dnnc::StrtabSection::Write(std::ostream&) const
dnnc::TensorSection::Write(std::ostream&) const
总结来说,虽然从这些接口看不出具体的内容,但是对整个编译器大致流程有了些简单的了解,至少能看出深鉴代码结构还是非常规范的,架构设计精巧,值得学习。

另外发现目前深鉴的样例中,不知是出于什么原因的考虑,VGG16网络中全连接层的计算并没有采用DPU加速,而是使用了ARM处理器的NEON并行指令来进行计算,从官方文档上的运行截图看,实际运行全连接层的GOPS仅为卷积层的几十甚至几百分之一。

全连接层计算时,会加载weights.bin和bias.bin文件,然后调用封装之后的函数来进行计算,包括了考虑是否有ReLU层的情况:

fc layer计算
void matxvec_add_vec_rowaccess(int rows, int cols, const float *matA, const float *matB, const float *matC, float *matR)
void matxvec_add_vec_relu_rowaccess(int rows, int cols, const float *matA, const float *matB, const float *matC, float *matR)

编译时需要加上选编译选项 -mfpu=neon,链接时加上库文件 -lhineon

你可能感兴趣的:(deep,learning,深鉴科技)