[Darknet源码] 1. 整体代码结构及 Makefile 详解

正午 2020-07-05 PM 68℃ 0条

Darknet 是一个深度学习框架,相对小巧轻量,不涉及计算图,自动微分等概念。darkent 的代码也没有太多抽象,没有 Tensor 的结构, 所有在网络中传输的对象都是用 float *完成的,所以读懂代码的门槛相对较低。但这也会带来一些问题,框架本身不能提供像 tensorflow 或者 pytorch 那样灵活的前端 API ,由于没有太多抽象,所以每个 layer 的实现都需要对底层的指针操作很熟悉。读代码的目的就是好奇 darknet 是怎么实现的。

先统计一下代码量,总共 3 万行左右, 核心 C 的代码应该 2 万左右,当然代码量不能说明太多问题,Redis 也才一万多行 C 代码。
截屏2020-07-05 下午2.47.23.png

构建方式

复杂项目的构建系统本身对我而言就是一个门槛,比如 Pytorch , Pytorch 最开始是基于 CPython 写的,所以要知道 CPython 的扩展怎么写,以及 Python 的构建工具怎么用等等。好在 Darkent 几乎没有第三方依赖,如果不实用 Opencv 和 cuda 和 OpenMP 就完全没依赖。拉代码直接编译。

git clone https://github.com/pjreddie/darknet.git
make

然后可以参考官方文档跑下cifar10

src 文件夹下是源码, darknet 下是一个一个 C 的 header 文件。examples 是一些具体任务相关的实现。
├── Makefile
├── README.md
├── backup
├── cfg
├── darknet
├── data
├── examples
├── include
├── python
├── scripts
└── src

Makefile 解读

对 Makefile 做些解释,方便在改动代码后知道如何编译运行。比如我基于 darknet 增加了一个 layer 如何添加到项目里编译

前面 5 行都是编译条件,类似开关一样的东西,0 就是关闭, 1 就是开启,这些条件会在后面的代码里用到,比如 #ifdef GPU 告诉编译器是否编译 GPU 部分的代码。

GPU=0
CUDNN=0
OPENCV=0
OPENMP=0
DEBUG=0

接下来定义了一些全局变量, 我把这写变量分为两类,一类是编译器相关,一类是项目本身相关的,我可能重点关注项目相关的,但还是先来看下编译器相关的,这又分为 GPU 和 CPU 两部分
GPU 相关的编译器是 NVCC

ARCH 是 GPU 编译相关的,我猜是和具体显卡有关的,具体可参考官方文档
CC 是指定的编译器,我想应该是 C Compiler 的意思吧,很多项目都这样用的,LDFLAGS= -lm -pthread 是需要链接的库, -lm应该是数学库 ,就是在代码里要用的 math.h -pthread 是线程

CPP=g++ 是指定 c++ 编译器
NVCC=nvcc 是指定 GPU 代码的编译器
AR=arARFLAGS=rcs ar 在这里是用来把多个目标文件打包成静态库的。

CFLAGS=-Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC 就是 C 编译器的一些参数。

ARCH= -gencode arch=compute_30,code=sm_30 \
      -gencode arch=compute_35,code=sm_35 \
      -gencode arch=compute_50,code=[sm_50,compute_50] \
      -gencode arch=compute_52,code=[sm_52,compute_52]
#      -gencode arch=compute_20,code=[sm_20,sm_21] \ This one is deprecated?
# This is what I use, uncomment if you know your arch and want to specify
# ARCH= -gencode arch=compute_52,code=compute_52

CC=gcc
CPP=g++
NVCC=nvcc 
AR=ar
ARFLAGS=rcs
OPTS=-Ofast
LDFLAGS= -lm -pthread 
CFLAGS=-Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC

再来看项目相关的一些变量 , VPATH 是定义了源码的路径是 srcexample
SLIB ALIB 包的名字
EXEC 是最终的可执行文件
OBJDIR 缓存目标文件
最后两行在 中间定义的,OBJ 定义目标文件,每个 c 文件都会对应一个目标文件
EXECOBJA 是可执行文件,也就是任务相关的代码生产的,比如分类目标检测等等,如果我增加了一个 layer 就在 OBJ 后面添加,如果 增加一个具体任务就在 examples下些代码,然后在 EXECOBJA 添加对象文件

VPATH=./src/:./examples
SLIB=libdarknet.so
ALIB=libdarknet.a
EXEC=darknet
OBJDIR=./obj

OBJ=gemm.o utils.o cuda.o deconvolutional_layer.o convolutional_layer.o list.o image.o activations.o im2col.o col2im.o blas.o crop_layer.o dropout_layer.o maxpool_layer.o softmax_layer.o data.o matrix.o network.o connected_layer.o cost_layer.o parser.o option_list.o detection_layer.o route_layer.o upsample_layer.o box.o normalization_layer.o avgpool_layer.o layer.o local_layer.o shortcut_layer.o logistic_layer.o activation_layer.o rnn_layer.o gru_layer.o crnn_layer.o demo.o batchnorm_layer.o region_layer.o reorg_layer.o tree.o  lstm_layer.o l2norm_layer.o yolo_layer.o iseg_layer.o image_opencv.o

EXECOBJA=captcha.o lsd.o super.o art.o tag.o cifar.o go.o rnn.o segmenter.o regressor.o classifier.o coco.o yolo.o detector.o nightmare.o instance-segmenter.o darknet.o

根据不同条件,然后对一些全集参数会做些修改, 比如 下面的是否开启 OPENCV

ifeq ($(OPENCV), 1) 
COMMON+= -DOPENCV
CFLAGS+= -DOPENCV
LDFLAGS+= `pkg-config --libs opencv` -lstdc++
COMMON+= `pkg-config --cflags opencv` 
endif

给所有的 目标文件加上前缀,也就是指定文件夹,DEPS 是头文件的依赖

EXECOBJ = $(addprefix $(OBJDIR), $(EXECOBJA))
OBJS = $(addprefix $(OBJDIR), $(OBJ))
DEPS = $(wildcard src/*.h) Makefile include/darknet.h

当我们输入 make 指令的时候会调用 all 命令,冒号后的都是一个指令, 按顺序执行。指令可以理解为一组 shell 脚本

all: obj backup results $(SLIB) $(ALIB) $(EXEC)

比如 obj 就是在创建目标文件的存储文件夹,backupresults 也是创建需要的文件夹。


obj:
    mkdir -p obj
backup:
    mkdir -p backup
results:
    mkdir -p results

需要解释下的是下面这组指令

$(EXEC): $(EXECOBJ) $(ALIB)
    $(CC) $(COMMON) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(ALIB)

$(ALIB): $(OBJS)
    $(AR) $(ARFLAGS) $@ $^

$(SLIB): $(OBJS)
    $(CC) $(CFLAGS) -shared $^ -o $@ $(LDFLAGS)

$(OBJDIR)%.o: %.cpp $(DEPS)
    $(CPP) $(COMMON) $(CFLAGS) -c $< -o $@

$(OBJDIR)%.o: %.c $(DEPS)
    $(CC) $(COMMON) $(CFLAGS) -c $< -o $@

$(OBJDIR)%.o: %.cu $(DEPS)
    $(NVCC) $(ARCH) $(COMMON) --compiler-options "$(CFLAGS)" -c $< -o $@

这组指令用了 Makefile 的通配符和特殊变量,$(OBJDIR)%.o: 相当于定义了多个指令 obj/gemm.o 这种,
从输出能看到具体执行的 shell

gcc -Iinclude/ -Isrc/ -Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC -Ofast -c ./src/gemm.c -o obj/gemm.o
gcc -Iinclude/ -Isrc/ -Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC -Ofast -c ./src/utils.c -o obj/utils.o
gcc -Iinclude/ -Isrc/ -Wall -Wno-unused-result -Wno-unknown-pragmas -Wfatal-errors -fPIC -Ofast -c ./src/cuda.c -o obj/cuda.o
标签: none

非特殊说明,本博所有文章均为博主原创。

评论