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

举报
染念 发表于 2023/11/30 16:03:44 2023/11/30
【摘要】 MIGraphX是一款用于DCU上的深度学习推理引擎,它的目的是为了简化和优化端到端的模型部署流程,包括模型优化、代码生成和推理。MIGraphX能够处理多种来源的模型,如TensorFlow和Pytorch,并提供用户友好的编程界面和工具,使得用户可以集中精力在业务推理开发上,而不需要深入了解底层硬件细节。@TOC 特性支持多种数据精度推理(如FP32,FP16,INT8)。提供C++和P...

MIGraphX是一款用于DCU上的深度学习推理引擎,它的目的是为了简化和优化端到端的模型部署流程,包括模型优化、代码生成和推理。MIGraphX能够处理多种来源的模型,如TensorFlow和Pytorch,并提供用户友好的编程界面和工具,使得用户可以集中精力在业务推理开发上,而不需要深入了解底层硬件细节。

特性

  • 支持多种数据精度推理(如FP32,FP16,INT8)。
  • 提供C++和Python的多语言API支持。
  • 支持动态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无法正常使用。
    • 安装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 用于描述数据的形状和类型。例如,假设有一个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<float>。第二个参数可以传入一个连续的数据指针(data方法),或者直接使用weights变量。

literal中常用的成员函数:

  • get_shape(): 与 Argument 中的同名函数类似,返回常量的形状。
  • data(): 返回指向常量数据的指针。不同于 ArgumentLiteral 中的数据不可修改。

target

表示支持的硬件平台,如CPU或GPU。

program

代表一个神经网络模型,提供编译和推理接口。

构造一个program,很简单:

migraphx::program net;

program中常用的成员函数:

  • 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表示子图,每个子图又是由指令组成。
例如,在主模块中添加输入:

 //获取主计算图
 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}});

module中常用的成员函数:

  • 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了。

附录

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。