技术文章

深度学习代码生成 | 如何快速将推理模型部署到生成环境中

作者 : 周拥华、MathWorks


如今,深度学习已在商业领域普及,现实中对训练好的推理模型的应用,已可见于各行各业,但具体应用的开发仍大量依赖一些大公司的框架或产品,他们有些提供云端的接口,也有些提供基于安卓的智能终端上的apk,换句话说,核心其实并没有掌握在自己手里,想深度定制就难了。而能够自行采用TensorRT、cuDNN或CUDA,或者利用Intel MKL-DNN,又或者针对ARM的Mali GPU或Neon核实现深度学习应用的,与普遍存在的需求相比仍在少数。原因可能是下面中的一条或多条:

  • 缺乏有相关编程经验的工程师
  • 目标平台不能用TensorRT,自己基于cuDNN或原生CUDA写太难了
  • 用TensorRT还能搞定NVIDIA GPU,ARM实在无能为力
  • 找来一套开源代码,BUG好多,动不动就死了,死了,了
  • 性能不达标但量化好坑,FP32的模型转FP16/INT8可咋整
  • 有Intel Xeon处理器没NVIDIA GPU,还得学MKL-DNN框架

【编者注:深度学习不仅训练要用到大量数据和算力,其推理也很耗计算资源,因此在实际应用中必须利用如MKL-DNN、CUDA、ARM Compute等在支持张量计算的硬件如Intel Xeon、NVIDIA GPU和ARM Mali或Neon核上实现才能获得满足实际要求的性能】

那有没有办法又快又稳地在不同目标平台上用C++轻松实现训练好的推理模型呢?

眼见为实,我们先看一个例子

假设我们有一个训练好的LSTM模型,存储在lstmnet.mat文件中【编者注:它可以是在开源框架中训练后按ONNX标准格式导入MATLAB的,也可以是在MATLAB中训练产生的】,我们写了一个MATLAB函数lstmnet_predict.m来调用这个模型进行推理,如下:

function out = lstmnet_predict(in)
persistent mynet;
if isempty(mynet)
    mynet = coder.loadDeepLearningNetwork('lstmnet.mat');
end
out = predict(mynet,in);

采用以下脚本【编者注:或者通过GPU Coder那个带向导提示的图形化APP】,即可为上述推理函数产生C++代码。

% 为代码生成进行配置
cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
% 定义目标函数的输入参数
matrixInput = coder.typeof(double(0),[3 Inf],[false true]);
% Run the codegen command.
codegen -config cfg lstmnet_predict -args {matrixInput} -report

自动生成的C++代码看起来是下面这个样子的:

事实上,通过修改代码生成配置,也就是下面这几行代码,我们就可以为不同的目标平台生成不同的代码了。

% 为代码生成进行配置
cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');

譬如,如果我们要基于TensorRT来生成嵌入式GPU(如Jetson Nano)上跑的代码:

% 为代码生成进行配置
cfg = coder.gpuConfig('lib');
cfg.TargetLang = 'C++';
cfg.GenCodeOnly = true;
cfg.Hardware = coder.hardware('NVIDIA Jetson');
cfg.DeepLearningConfig = coder.DeepLearningConfig('tensorrt');
cfg.GpuConfig.ComputeCapability = '7.0';
% cfg.DeepLearningConfig.DataType = 'fp16';
% 定义目标函数的输入参数
matrixInput = coder.typeof(double(0),[3 Inf],[false true]);
% Run the codegen command.
codegen -config cfg lstmnet_predict -args {matrixInput} -report

这里,我们可以指定硬件平台与兼容版本,甚至通过增加一行DataType的配置就可以从默认的FP32转而生成FP16的推理代码了。

【编者注:转成INT8稍微复杂一些,因为那不仅要防止溢出,还必须进行校验校准。以一个图像分类网络如mobilenet v2或resnet-50为例,如果要产生INT8的代码,我们需要准备一个数据集用来在代码生成时进行校验校准,但代码生成时的配置其实也就是3行脚本:

cfg.DeepLearningConfig.DataType = 'int8'; 
cfg.DeepLearningConfig.DataPath = 'calib_dataset';
cfg.DeepLearningConfig.NumCalibrationBatches = 50; 

另外,最新的MATLAB R2020a有一个Deep Network Quantizer附加功能可以用来简化这一工作。】

再譬如,如果要针对ARM Neon生成C++代码,脚本如下:

% 为代码生成进行配置
cfg = coder.config('lib');
cfg.TargetLang = 'C++';
cfg.GenCodeOnly = true;
dlcfg = coder.DeepLearningConfig('arm-compute');
dlcfg.ArmArchitecture = 'armv7';
dlcfg.ArmComputeVersion = '19.05';
cfg.DeepLearningConfig = dlcfg;
% cfg.Hardware = coder.hardware('Raspberry Pi');
% 定义目标函数的输入参数
matrixInput = coder.typeof(double(0),[3 Inf],[false true]);
% Run the codegen command.
codegen -config cfg lstmnet_predict -args {matrixInput} -report

这里用到了ARM Compute 库,我们不仅指定了ARM的架构版本,还指定了Compute库的版本。

【编者注:Arm Compute Library与MATLAB的版本最好按帮助文档的提示进行对应,因为那是验证过而且对应优化过的,能保证稳定性和性能最优;另外,由于编译器兼容性的问题,建议参考帮助文档,自行从源代码编译安装ARM Compute library到你的嵌入式硬件上。】

给ARM Neon生成的代码看起来是这样子的:

【编者注#1:显然,仅仅生成C++代码是不够的,推理模型还含有数以百万计的训练好的参数呢,那肯定也是一并生成好了的,而且在生成的代码中会自动完成这些数据文件的加载和参数初始化,您在应用程序中只需要对接口进行调用即可】。

【编者注#2:有些深度学习推理模型的接口也很复杂,生成后调用起来也要搞清楚很多东西才能玩得转,能不能生成一个主程序给做个参考呀?答案是肯定的,你可以把MATLAB里写的前处理和后处理也包含在代码生成中,然后再在代码生成配置中增加这样一行即可:

cfg.GenerateExampleMain = 'GenerateCodeAndCompile';

】。

【编者注#3:自动生成的文件这么多,能自动产生makefile吗?必须能,不仅可以自动产生makefile,还可以自动通过调用开发机或者目标机的编译器,以本地编译或交叉编译的方式进行编译,从而产生可执行文件、动态库或静态库】

MATLAB都能为深度学习推理模型生成哪些目标平台的代码呢?

当前MATLAB R2020a支持的目标平台包括:

  • NVIDIA GPU,支持TensorRT和cuDNN,兼容Windows、Linux及基于Linux的嵌入式GPU平台(如NVIDIA Jetson系列和Drive系列)
  • ARM Cortex-A系列,含Neon核,支持ARM Compute Library
  • ARM Mali GPU,支持ARM Compute Library
  • Intel Xeon,AVX/SSE指令集,支持Intel MKL-DNN

是不是所有的深度网络都能支持代码生成?

这是MATLAB的目标,当前还做不到这一点,可以说目前支持的网络有很多种【以CNN为主】,具体则要看实际的深度网络用到了哪些层以及这些层MATLAB是否已经支持代码生成。有些情况我们可以略作变通也能生成代码,譬如YOLOv3当前版本并不支持训练也不能直接从外部导入模型后生成代码,但只需要在导入时将其中几层稍作替换就可以实现,本文限于篇幅就不详细介绍了,留待以后再来介绍。

在MATLAB的文档中,有专门的篇幅介绍MATLAB的深度学习代码生成,里面包含了一些参考例子,我们列举其中一部分在这里供大家参考,全文可点击链接查看详情:

GPU 代码生成

CPU 代码生成

2022 年发布

查看文章,了解相关行业