MIGraphX是一款用于DCU上的深度学习推理引擎,它的目的是为了简化和优化端到端的模型部署流程,包括模型优化、代码生成和推理。MIGraphX能够处理多种来源的模型,如TensorFlow和Pytorch,并提供用户友好的编程界面和工具,使得用户可以集中精力在业务推理开发上,而不需要深入了解底层硬件细节。
特性
• 支持多种精度推理,比如FP32,FP16,INT8
• 支持多语言API,包括C++和Python
• 支持动态shape 、模型序列化
• 支持调试
• 提供性能分析⼯具
MIGraphX的架构分为三个主要层次:
•中间表示层:这一层将训练好的模型(例如ONNX格式)转换成MIGraphX的内部表示(IR)格式,即计算图。所有的模型优化和代码生成都是基于这个计算图完成的。
•编译优化层:在这一层,MIGraphX对中间表示进行各种优化,如常量折叠、内存复用和算子融合等,从而提高推理性能。
•计算引擎层:这一层包含底层计算库的接口,例如MIOpen和rocblas。MIGraphX的后端实现主要通过调用这些库来完成。
MIGraphX采用单级IR设计,简化编译优化过程。它支持各种模型,包括CNN、LSTM、Transformer等。 我们可以使用migraphx-driver onnx -l查看支持的onnx算子。
安装方法
MIGraphX可以通过镜像或安装包的方式进行安装。使用镜像是推荐的安装方法,用户可以从指定的下载地址获取合适的镜像。此外,也可以选择根据系统不同下载相应的安装包。安装过程中需要设置环境变量和路径,确保MIGraphX能够正常使用。具体如下:
- 使用镜像(推荐) 下载地址,根据需要选择合适的镜像
例如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无法正常使用。
- 安装half
wget 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_PATH
和export 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
用于描述数据的形状和类型。例如,假设有一个3通道的224x224的图像,其 Shape 可以这样表示:migraphx::shape{migraphx::shape::float_type, {1, 3, 224, 224}}
其中 float_type 表示数据类型为浮点型,{1, 3, 224, 224} 分别代表批量大小(batch size)、通道数(channel)、高度和宽度,如果是变量传入,它得是std::vector类型的。
构造函数还有第三个可选参考:std::vector
类型,它用来表示每一维度的步长,如果没有指定步长,则按照shape为standard的形式根据l自动计算出步长,比如对于一个内存排布为 [N,C,H,W]格式的数据,对应的每一维的步长为[C H W,H * W,W,1]。
其中:
- 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
shape中常用的成员函数: lens()
: 返回每一维的大小。例如,shape.lens()
可能返回{1, 3, 224, 224}
,表示批量大小、通道数、高度和宽度。elements()
: 返回所有元素的个数。例如,shape.elements()
可能返回1*3*224*224
。bytes()
: 返回所有元素的字节数。例如,对于浮点类型的数据,shape.bytes()
可能返回1*3*224*224*sizeof(float)
。
在shape中,无论是图像还是卷积,都要是NCHW格式的,例如有一卷积核大小为7x7,输出特征图个数为64,输入的是一个3通道的图像,则该卷积核的shape可以表示为migraphx::shape{migraphx::shape::float_type, {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,常用来保存模型的输入和输出数据。
假设 inputData 是一个 cv::Mat 对象,代表输入图像数据,则 Argument 可以这样构建:migraphx::argument input = migraphx::argument{inputShape, (float*)inputData.data};
这里 inputShape 是数据的 Shape,(float)inputData.data 是数据的指针。argument不会自动释放该数据。
当然,*可以只需要提供shape就可以,系统会自动申请一段内存,该内存的大小等于shape的bytes()方法返回值的大小。
argument中常用的成员函数:get_shape()
: 返回数据的形状。例如,argument.get_shape()
。data()
: 返回指向数据的指针。例如,argument.data()
可以用来访问或修改存储在Argument
中的推理结果。
前面介绍了cv::Mat转换migraphx::argument,它也支持重新转回去的。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是一种特殊的 argument。
如果有一个权重数组 weights,其 Shape 为 weightShape,则可以这样创建一个 Literal:migraphx::literal weightLiteral = migraphx::literal{weightShape, weights.data()};
这里设 weights 是一个包含权重数据的标准容器,如 std::vector。第二个参数可以传入一个连续的数据指针(data方法),或者直接使用weights
变量。
literal中常用的成员函数:get_shape()
: 与Argument
中的同名函数类似,返回常量的形状。data()
: 返回指向常量数据的指针。不同于Argument
,Literal
中的数据不可修改。target
表示支持的硬件平台,如CPU或GPU。program
代表一个神经网络模型,提供编译和推理接口。
构造一个program,很简单:
program中常用的成员函数:migraphx::program net;
compile(target, options)
: 编译模型。target
参数指定硬件平台,options
提供编译设置。比如可以通过options.device_id设 置使用哪一块显卡。eval(params)
: 执行推理并返回结果。params
是一个包含模型输入的parameter_map
。parameter_map类型是std::unordered_map< std::string, argument>
(哈希容器)的别名。注意这是一个同步的方法。get_parameter_shapes()
: 返回模型输入或输出参数的形状。返回哈希容器类型。get_main_module()
: 返回程序的主计算图,通常用于添加或修改模型层。module
创建program的时候,会自动创建一个主计算图。而现代神经网络模型中可能存在多个子图,MIGraphX中使用module表示子图,每个子图又是由指令组成。
例如,在主模块中添加输入:
module中常用的成员函数://获取主计算图 migraphx::module *mainModule = net.get_main_module(); // 添加模型的输入 migraphx::instruction_ref input =mainModule->add_parameter("input", migraphx::shape{migraphx::shape::float type, {1, 1,4,6}});
add_parameter(name, shape)
: 添加模型输入。name
是输入的名称,shape
是输入的形状。返回值表示添加到模型中的该条指令的引用。
add_literal(literal)
: 向模块添加一个Literal
对象。返回值表示添加到模型中的该条指令的引用。add_instruction(op, args)
: 添加指令。op
是算子,args
是算子的参数。返回值表示添加到模型中的该条指令的引用。add_return(args)
: 添加结束指令,通常表示模型的输出。MIGraphX中使用instruction_ref这个类型表示指令的引用
instruction
instruction表示指令,可以通过module中的add_instruction()成员函数添加指令。MIGraphX中的指令相当于ONNX模型中的一个节点或者caffe模型中的一个层。指令由操作符(算子)和操作数组成。
view
视图(view)操作是一种重要的内存管理技术。它允许不同的张量(tensor)共享相同的数据存储,而不需要复制数据。这种方法既节省内存,又提高效率。
假设你有一个4x4的随机张量t:t = torch.rand(4,4)
你可以使用view()方法创建一个2x8的视图b:
b = t.view(2, 8)
在这个例子中,b是t的一个视图。如果你更改b中的任何元素,t中相应的元素也会改变。例如:
b[0][0] = 3.14 print(t[0][0]) # 输出将会是 3.14
MIGraphX中的视图操作与PyTorch相似,但用于处理
argument
对象。你可以创建一个argument
的视图,这个视图与原始`argument
共享内存。支持的操作有broadcast、slice、transpose、reshape。
考虑一个4行6列的数组,它按照行主序存储。如果你想对这个数组进行切片操作(比如取出中间的一部分),你可以创建一个视图,这个视图会指向原始数组的一个特定区域,而不复制任何数据。□ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □ □
你可以创建一个视图来实现切片操作,该切片操作参数为:starts=[0,2],ends =[4,5],steps = [1, 1] ,切片操作的结果为原二维数组的一个视图,该视图与原数据共享内存,该视图如下所示。
切片左闭右开,实际上应该是[0,2]到[3,4]0 1 2 3 4 5 0 □ □ ■ ■ ■ □ 1 □ □ ■ ■ ■ □ 2 □ □ ■ ■ ■ □ 3 □ □ ■ ■ ■ □
原来的二维数据的shape用
{migraphx::shape::float_type, {4,6},{6,1}}
表示,通过切片变成了4×3的数值,但由于与原始数据共享内存,因此视图的步长仍然是[6,1],即新建的数据shape是{migraphx::shape::float_type, {4,3},{6,1}}
// 视图包含的成员 { float *data_ptr; std::vector<std::size_t> lens; std::vector<std::size_t> strieds; }
当你使用PyTorch的view()方法创建了一个视图后,可以像访问普通张量一样访问视图中的元素。
假设我们有一个4x4的张量A,并且创建了一个2x8的视图B。要访问B中的特定元素,你可以直接使用索引。import torch A = torch.rand(4, 4) B = A.view(2, 8) # 访问B中的第1行第2列的元素 element = B[1][2] print("Accessed Element:", element)
在这个例子中,当你通过索引[1][2]访问B时,你实际上访问的是A中对应位置的数据,因为它们共享内存。
在MIGraphX中,访问视图元素的原理类似。
在视图中访问元素时,通过形状可以正确访问数据。例如,访问视图中第2行第1列的元素"🫣",其二维索引为[1,0],在实际内存中的索引为索引与步长的内积,即1 6 + 0 1 = 6。因此,这个元素在内存中的位置为data_ptr + 6,“😜”是视图的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了。附录
- https://rocm.docs.amd.com/projects/AMDMIGraphX/en/latest/
- https://cancon.hpccube.com:65024/4/main/inferexamples