还没了解MIGraphX推理框架?试试这篇让你快速入门

MIGraphX是一款用于DCU上的深度学习推理引擎。MIGraphX能将深度学习框架(Tensorflow,Pytorch等)训练好的算法模型转换为MIGraphX IR表示的计算图,并提供端到端的模型优化、代码⽣成以及推理业务部署能⼒ 。 MIGraphX致⼒于为用户提供灵活、易⽤的编程接⼝以及配套⼯具,让用户能够专注于推理业务开发和部署本⾝ ,而无需过多关注底层硬件细节,显著提高用户的开发效率。

特性

  • 支持多种精度推理,比如FP32,FP16,INT8
  • 支持多语言API,包括C++和Python
  • 支持动态shape
  • 支持模型序列化
  • 支持调试
  • 提供性能分析⼯具

整体架构

  • 中间表示层:用户训练好的算法模型(onnx)会统⼀转换为用MIGraphX IR 表示的计算图,后续的模型优化和代码生成都基于该计算图完成。
  • 编译优化层:基于MIGraphX IR完成各种优化,比如常量折叠,内存复用优化,算子融合等,提高推理性能。
  • 计算引擎层:主要包含了底层计算库的接口,包括MIOpen和rocblas,MIGraphX后端的实现主要是通过调用计算库的方式实现的

AI编译中的IR从层级上分一般可以分为两种类型:多级IR和单级IR。使用多级IR可以使得系统优化更加灵 活,各级IR只需要负责本级优化,多级IR的代表就是MLIR,但是多级IR会带来如下的问题:

  1. 需要在不同IR之间进行转换,IR转换做到完全兼容很难而且工作量大。
  2. 不同IR转换可能带来信息的损失。
  3. 多级IR有些优化既可以在上一层IR进行, 也可以在下一层IR进行, 让系统开发者很难选择。 MIGraphX采用了单级IR的设计,这种形式的IR可以表达计算图中的控制流信息和数据依赖关系,方便后 面的编译优化。

MIGraphX采用静态图模式,在编译优化阶段,MIGrahpX实现了如下的优化:

  1. 机器无关优化:比如删除公共子表达式,删除无用的代码,常量传播,常量折叠,代数化简,算子 融合等。
  2. 内存复用优化:MIGraphX采用了图着色的方法实现无计算依赖的节点间的内存复用,显著减低内 存消耗。
  3. 指令调度:根据计算图分析指令之间的依赖关系,根据这些依赖关系优化各指令的执行顺序,从而 提高计算性能。

支持的算子

migraphx-driver onnx -l查看支持的onnx算子

支持的模型

目前 MIGraphX支持常用的 CNN 、LSTM 、Transformer和Bert等模型:

  1. Classification:AlexNet,VGG,Inception,ResNet,DenseNet,EfficientNet等
  2. Detection :SSD,YOLO,DBNet等
  3. Segmentation :FCN,UNet,MaskRCNN等
  4. LSTM:CRNN等
  5. Transformer:Vision Transformer(ViT)等
  6. BERT:BERT-Squad等

安装方法

  • 使用镜像(推荐) 下载地址,根据需要选择合适的镜像

例如docker pull image.sourcefind.cn:5000/dcu/admin/base/migraphx:4.0.0-centos7.6-dtk23.04.1-py38-latest

在使用MIGraphX之前,需要设置容器中的环境变量:source /opt/dtk/env.sh,如果需要在python中使用migraphx,还需要设置PYTHONPATH :export PYTHONPATH=/opt/dtk/lib:$PYTHONPATH

  • 使用安装包,安装包下载地址,根据不同的系统选择合适的安装包
    • 安装dtk,上面的光源dtk镜像或者安装包,然后将下载好的安装包安装到/opt目录下,最后创建一个软连接/opt/dtk,使得该软连接指向dtk的安装目录,注意:一定要创建软连接/opt/dtk,否则MIGraphX无法正常使用。
    • 安装halfwget https://github.com/pfultz2/half/archive/1.12.0.tar.gz,解压(tar -xvf ...tar.gz)后将include目录下的half.hpp拷贝到dtk目录下的include目录:cp half-1.12.0/include/half.hpp /opt/dtk/include/
    • 安装sqlite:下载地址,解压,切换目录,然后./configure && make && make install,最后设置环境变量:export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATHexport LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH
    • 下载MIGraphX: centos还要同时下载devel包
    • 设置环境变量:source /opt/dtk/env.sh,如果需要在python中使用migraphx,还需要设置PYTHONPATH :export PYTHONPATH=/opt/dtk/lib:$PYTHONPATH
    • 验证是否安装成功:/opt/dtk/bin/migraphx-driver onnx -l,输出支持的算子即可

编程模型

shape

用来表示数据的形状。

可以通过如下方式构造一个shape对象:

  • shape(type_t t, std::vector < std::size_t > l);
  • shape(type_t t, std::vector < std::size_t > l, std::vector < std::size_t > s);

其中:

  • t:shape的类型,shape支持的类型包括:bool_type,half_type,float_type,double_type,uint8_type,int8_type,uint16_type,int16_type,int32_type,int64_type,uint32_type,uint64_type
  • l:每一个维度的大小
  • s:每一个维度的步长,如果没有指定步长,则按照shape为standard的形式根据l自动计算出步长,比如对于一个内存排布为 [N,C,H,W]格式的数据,对应的每一维的步长为[C * H * W,H * W,W,1]

shape中常用的成员函数:

  • const std::vector<std::size_t>& lens() const 返回每一维的大小,维度顺序为(N,C,H,W)
  • std::size_t elements() const 返回所有元素的个数
  • std::size_t bytes() const 返回所有元素的字节数

示例: resnet50中第一个卷积层的卷积核大小为7x7,输出特征图个数为64,即有64个7x7的卷积核,如果输入的是一个3通道的图像,则该卷积核的shape可以表示为migraphx::shape{migraphx::shape::float_type, {64, 3, 7, 7}},其中float_type表示shape的数据类型,这里采用float类型, {64, 3, 7, 7}表示每一个维度的大小,注意{64, 3, 7, 7}对应的是NCHW的内存模型,由于这里没有提供每一维的步长,所以步长会自动计算。自动计算出来的每一维的步长为{147,49,7,1},所以完整的shape表示为{migraphx::shape::float_type, {64, 3, 7, 7},{147,49,7,1}}

对于该卷积核的shape,lens()函数的返回值为{64, 3, 7, 7},elements()的返回值为9408, bytes()的返回值为9408*4=37632。一个float占4个字节。

argument

用来保存数据,类似Pytorch中的Tensor,常用来保存模型的输入和输出数据。

可以通过如下方式构造一个argument对象:

  • argument(const shape& s)
  • template<class T> argument(shape s, T* d)

第1种方式只需要提供shape就可以,系统会自动申请一段内存,该内存的大小等于shape的bytes()方法返回值的大小。

第2种方式除了提供shape之外,还需要提供该argument的数据指针,argument不会自动释放该数据。

argument中常用的成员函数:

  • const shape& get_shape() const 返回数据的形状
  • char* data() const 返回argument的数据,可以通过data()的返回值访问推理结果。

与cv::Mat之间的转换

cv::Mat转换为migraphx::argument:

c++ 复制代码
cv::Mat inputData;// inputData表示一张224x224的3通道图像,数据类型为float类型,且为NCHW
形式
migraphx::shape inputShape=migraphx::shape{migraphx::shape::float_type, {1, 3,224, 224}};
migraphx::argument input= migraphx::argument{inputShape,(float*)inputData.data};// 注意,migraphx::argument不会释放inputData中的数据

migraphx::argument转换为cv::Mat:

c++ 复制代码
migraphx::argument result;// result表示推理返回的结果,数据布局为NCHW
int shapeOfResult[]={result.get_shape().lens()[0],result.get_shape().lens()
[1],result.get_shape().lens()[2],result.get_shape().lens()[3]};// shapeOfResult表
示的维度顺序为N,C,H,W
cv::Mat output(4, shapeOfResult, CV_32F, (void *)(result.data()));// 注意,cv::Mat
不会释放result中的数据

literal

使用literal表示常量,比如可以使用literal表示卷积的权重。实际上literal是一种特殊的 argument,literal中的值不能修改,而argument中的值可以修改。

可以通过如下方式构造一个literal对象:

  • template<class T> literal(const shape& s, const std::vector<T>& x)
  • template<class T> literal(const shape& s, T* x)
  • template<class T> literal(const shape& s, const std::initializer_list<T> &x)

第一种构造方法是使用std::vector来创建一个常量,第二种使用数据指针来构造,第三种是使用 std::initializer_list来构造。

literal中常用的成员函数:

  • const shape& get_shape() const 返回常量的形状
  • const char* data() const返回常量的数据指针,注意:不能通过data()返回的指针修改literal的值

target

target表示支持的硬件平台,目前支持CPU模式和GPU模式,在编译模型的时候,需要指定一个target。

program

表示一个神经网络模型

program中常用的成员函数:

  • void compile(const target& t, compile_options options = compile_options{}) 编译模型。第一个参数t是一个target,第二个参数options表示编译的一些设置,比如可以通过options.device_id设 置使用哪一块显卡。
  • std::vector<argument> eval(parameter_map params) const 执行推理并返回推理结果,参数params表示模型的输入数据,params中保存模型每个输入节点对应的输入数据, parameter_map类型是std::unordered_map< std::string, argument>的别名,注意这是一个同步的方法。
  • std::unordered_map<std::string, shape> get_parameter_shapes() 返回模型的输入或输出参数信息,常用来获取模型的输入参数信息。
  • module* get_main_module() 获取主计算图,module表示模型中的子图

std::unordered_map是哈希容器,它可以存储一组键值对,并且支持快速的查找、插入和删除操作

module

现代神经网络模型中可能存在多个子图,MIGraphX中使用module表示子图,每个子图又是由指令组成。 创建program的时候,会自动创建一个主计算图,可以通过program的get_main_module()方法获取主计算图。

module中常用的成员函数:

  • instruction_ref add_parameter(std::string name, shape s) 主要用来添加模型的输入,name表示输入名,s表示输入形状,返回值表示添加到模型中的该条指令的引用。
  • instruction_ref add_literal(literal l) 添加常量,比如可以使用该成员函数添加卷积算子的权重,返回值表示添加到模型中的该条指令的引用。
  • instruction_ref add_instruction(const operation& op, std::vector args) 添加指令,第一个参数op表示算子,args表示算子的参数,返回值表示添加到模型中的该条指令的引用。
  • instruction_ref add_return(std::vector args) 添加结束指令,通常表示模型的结尾,args表示模型最后的指令。

注意:

  • add_parameter(),add_literal(),add_return()添加的是模型中特殊的指令,这些指令不能使用add_instruction()添加, add_instruction()一般用来添加除了输入,常量和结束指令之外的其他指令。
  • 上述所有添加指令的成员函数返回添加的这条指令的引用,MIGraphX中使用instruction_ref这个类型表示指令的引 用,后续指令如果需要使用该条指令作为输入,可以通过该引用来获取该指令。

instruction

instruction表示指令,可以通过module中的add_instruction()成员函数添加指令。MIGraphX中的指令相当 于ONNX模型中的一个节点或者caffe模型中的一个层。指令由操作符(算子)和操作数组成。

视图

我们知道Pytorch中支持视图操作(view),Pytorch中一个tensor可以是另一个tensor的视图,视图tensor与原tensor 共享内存,视图可以避免不必要的内存拷贝,让操作更加高效。比如我们可以通过view()方法获取一个tensor的视 图:

python 复制代码
t = torch.rand(4,4)
b = t.view(2,8)#创建视图
t.storage().data_ptr() == b.storage().data_ptr() #b和t共享内存,返回True
b[0][0] = 3.14
print(t[0][0]) # 3.14

与Pytorch一样,MIGraphX也支持视图,一个argument可以是另一个argument的视图,视图和原argument共享内存, MIGraphX中支持视图的操作有

  • broadcast
  • slice
  • transpose
  • reshape

下面表示一个4行6列的二维数组,该数组按照行主序的方式在内存中连续存储(与C语言中的数组一致),所以在列这个维度上步长为1,在行这个维度上的步长为6,假设该二维数组的数据类型为float类型,则该二维数组的shape可以表示为{migraphx::shape::float_type, {4,6}},这里没有显式指定每一维的步长,migraphx会自动计算出步长:{migraphx::shape::float_type, {4,6},{6,1}}。

□ □ □ □ □ □
□ □ □ □ □ □  
□ □ □ □ □ □
□ □ □ □ □ □

现在有一个切片操作(slice),该切片操作参数为:starts=[0,2],ends =[4,5],steps = [1, 1] ,切片操作的结果为原二维数组的一个视图,该视图与原数据共享内存,该视图如下所示。

切片左闭右开,实际上应该是[0,2]到[3,4]

  0 1 2 3 4 5
0 □ □ ■ ■ ■ □
1 □ □ ■ ■ ■ □  
2 □ □ ■ ■ ■ □
3 □ □ ■ ■ ■ □

具体实现的时候,视图包含一个数据指针以及该数据的shape,为了方便说明,将shape拆分为2个部分表示:每一 维的大小和步长,本示例中该视图的数据指针指向原数组第三个元素,该视图的shape可以表示为{migraphx::shape::float_type, {4,3},{6,1}},所以视图中的成员lens为[4,3],strides为[6,1],注意由于与原数据共享内 存,所以该视图的步长为[6,1]而不是[3,1]。

c++ 复制代码
// 视图包含的成员
{
    float *data_ptr;
    std::vector<std::size_t> lens;
    std::vector<std::size_t> strieds;
}

视图中元素的访问

通过shape可以访问到正确的视图中的数据,比如要访问该视图的第2行第1列的元素"🫣",该元素在视图中的二维索引index可以表示为[1,0],则在实际内存中的索引(相当于"😜")为二维索引和步长的内积: index*strides=1 * 6 + 0 * 1 =6,"😜"是视图的data_ptr,则二维索引为[1,0]表示的数据在内存中对应的数据为data_ptr+6,所以可以通过二维索引与步长的内积得到实际的内存索引。

  0 1 2  3 4 5
0 □ □ 😜 ■ ■ □
1 □ □ 🫣 ■ ■ □  
2 □ □  ■ ■ ■ □
3 □ □  ■ ■ ■ □

MIGraphX中部分算子是不支持输入视图的,所以对于这些算子,如果输入的是一个视图,就需要通过contiguous操 作将内存变得连续。对于上面slice操作返回的视图,contiguous算子会创建一个新的内存空间,将转换后得到的内存连续的数据保存在新的内存空间中。contiguous算子的输出的shape可以表示为{migraphx::shape::float_type, {4,3},{3,1}},此时行步长是3而不是之前共享内存时的6了。

附录

相关推荐
公众号Codewar原创作者19 分钟前
R数据分析:工具变量回归的做法和解释,实例解析
开发语言·人工智能·python
IT古董35 分钟前
【漫话机器学习系列】020.正则化强度的倒数C(Inverse of regularization strength)
人工智能·机器学习
进击的小小学生37 分钟前
机器学习连载
人工智能·机器学习
Trouvaille ~1 小时前
【机器学习】从流动到恒常,无穷中归一:积分的数学诗意
人工智能·python·机器学习·ai·数据分析·matplotlib·微积分
dundunmm1 小时前
论文阅读:Deep Fusion Clustering Network With Reliable Structure Preservation
论文阅读·人工智能·数据挖掘·聚类·深度聚类·图聚类
szxinmai主板定制专家1 小时前
【国产NI替代】基于FPGA的4通道电压 250M采样终端边缘计算采集板卡,主控支持龙芯/飞腾
人工智能·边缘计算
是十一月末1 小时前
Opencv实现图像的腐蚀、膨胀及开、闭运算
人工智能·python·opencv·计算机视觉
云空1 小时前
《探索PyTorch计算机视觉:原理、应用与实践》
人工智能·pytorch·python·深度学习·计算机视觉
杭杭爸爸1 小时前
无人直播源码
人工智能·语音识别
Ainnle2 小时前
微软 CEO 萨提亚・纳德拉:回顾过去十年,展望 AI 时代的战略布局
人工智能·microsoft