当前位置:首页 > 行业发展 > 正文

构建基于 WasmEdge 与 WASI-NN 的 OpenVINO 的道路分割推理任务

如今,AI推理这个词已经不是什么陌⽣的词汇了。从技术层面上讲,AI推理的路径是输入数据,调用模型,返回结果。这看起来是完美的serverless函数[1],因为AI推理有简单的输入与输出,并且是无状态的。众所周知,AI推理是一项计算密集型工作。使用WebAssembly和Rust可以实现高性能的AI...

如今,AI推理这个词已经不是什么陌⽣的词汇了。从技术层面上讲,AI推理的路径是输入数据,调用模型,返回结果。这看起来是完美的serverless函数[1],因为AI推理有简单的输入与输出,并且是无状态......

如今,AI推理这个词已经不是什么陌⽣的词汇了。从技术层面上讲,AI推理的路径是输入数据,调用模型,返回结果。这看起来是完美的serverless函数[1],因为AI推理有简单的输入与输出,并且是无状态的。众所周知,AI推理是一项计算密集型工作。使用WebAssembly和Rust可以实现高性能的AI推理函数,同时通过Wasm保证函数的安全与跨平台易用性。

最近,WasmEdgeRuntime[2]的0.10.1版本已经提供了对WASI-NN[3]接口提案的支持,后端推理引擎部分目前仅支持IntelOpenVINO。

不过,根据7月份的WasmEdge社区会议上公布的开发计划,WasmEdge后续会逐步支持TensorRT、PyTorch[5]、ONNXRuntime等后端推理引擎。那么,如何使用这套新的接口规范来构建基于WebAssembly技术的AI推理任务?开发流程是什么样子的?复杂程度又如何?

在这篇文章我们尝试通过一个简单的道路分割ADAS例子来回答这些问题。以下所涉及的示例代码及相关文件,可前往WasmEdge-WASINN-examples[6]代码库查看下载,也欢迎你添加更多wasi-nnexample。

内容大纲

WASI-NN是什么?

在示例之前,我们简单介绍一下WASI-NN接口提案。

实际上,WASI-NN提案的名字是由两个部分构成:WASI是WebAssemblySystemInterface的缩写,简单来说,WASI定义了一组接口规范,从而允许WebAssembly在更细粒度的权限控制下,安全地运行在非浏览器的环境中;NN代表NeuralNetwork,即神经网络。显而易见,WASI-NN是WASI接口规范的一个组成部分,其主要针对机器学习这个应用场景。

理论上来说,这个接口规范既可以用于模型训练,亦可以用于模型推理,我们的示例仅针对模型推理这个部分。关于WASI及WASI-NN接口规范的更多细节,可前往[7]进一步了解。

目前,WasmEdgeRuntime项目的主分支上已经提供了稳定的WASI-NN支持,涵盖了WASI-NNProposalPhase2所定义的五个主要接口:

//加载模型的字节序列load:function(builder:graph-builder-array,encoding:graph-encoding,target:execution-target)-expectedgraph,error//创建计算图执行实例init-execution-context:function(graph:graph)-expectedgraph-execution-context,error//加载输入set-input:function(ctx:graph-execution-context,index:u32,tensor:tensor)-expectedunit,error//执行推理compute:function(ctx:graph-execution-context)-expectedunit,error//提取结果get-output:function(ctx:graph-execution-context,index:u32)-expectedtensor,error

这些接口的主要作用就是为实现Wasm模块与本地系统资源之间的“互通”提供管道。在推理任务中,前端(Wasm模块)和后端(推理引擎)之间的数据就是通过这个管道来完成的。下图是WasmEdgeRuntime的WASI-NN接口应用简图。图中,绿色矩形所表示的WASI-NN接口将前端的Wasm模块与后端的OpenVINO推理引擎进行了“绑定”。下文示例在执行推理阶段,实际上就是通过WasmEdgeRuntime内建的WASI-NN接口,在前、后端之间完成数据交互、函数调度等一系列工作。

下面,就结合具体的例子,来实战一下如何基于WasmEdgeRuntime来构建一个“简约而不简单”的机器学习推理任务。

使用OpenVINO进行道路切割ADAS

在动手之前,我们先来定义一下使用WASI-NN接口构建机器学习推理任务的大致流程,以便对各阶段的任务有个总体把握。

任务1:定义推理任务、获取推理模型和输入

任务2:环境准备

任务3:构建wasm推理模块

任务4:执行wasm模块。通过WasmEdgeRuntime提供的命令行执行模式,即standalone模式,执行任务3中创建的wasm模块,完成推理任务。

任务5:可视化推理任务的结果数据。

下面我们就详细描述一下如何完成上述各项任务目标。

任务1:推理模型和输入图片的获取

在推理模型的选择上,为了方便起见,我们在Intel官方的openvino-model-zoo[8]开源代码库中选择了road-segmentation-adas-0001模型。这个模型主要用于自动驾驶场景下,完成对道路进行实时分割的任务。为了简化示例的规模,我们仅使用图片作为推理任务的输入。

任务2:环境准备

我们选择作为系统环境。WasmEdge项目也提供了自己的开发环境,所以想简化环境准备过程的同学,可以从dokerhub[9]上拉取系统镜像。除了系统环境外,还需要部署一下安装包:

安装

安装

安装

可选:安装JupyterNotebook

安装Rustup编译工具链

环境准备完毕后,就可以下载示例项目的代码和相关文件。本次示例项目的完整代码和演示用的相关文件存放在WasmEdge-WASINN-examples/openvino-road-segmentation-adas[11],可以使用下面的命令下载:

//下载示例项目gitclonegit@:second-state///进入到本次示例的根目录cdWasmEdge-WASINN-examples/openvino-road-segmentation-adas/rust//查看示例项目的目录结构tree.

示例项目的目录结构应该是下面这个样子:

.├──├──image│└──empty_road_(示例中用作推理任务输入的图片)├──image-preprocessor------------------------(Rust项目,用于将输入图片转换为OpenVINOtensor)│├──│├──│└──src│└──├──model---------------------------------------(示例中所使用的OpenVINO模型文件:xml文件用于描述模型架构,bin文件存放模型的权重数据)│├──│└──├──openvino-road-segmentation-adas-0001--------(Rust项目,其中定义了wasi-nn接口调用逻辑。编译为wasm模块,通过WasmEdgeCLI调用执行)│├──│├──│└──src│└──├──tensor--------------------------------------()│├──(该二进制文件由输入图片转化而来,作为wasm推理模块的一个输入)│└──(该二进制文件保存了wasm推理模块产生的结果数据└──visualize_inference_(用于可视化数据)

根据上面目录结构中的注释,各位同学应该对这个示例项目的各个部分有了大概的了解。这里再说明几点:

示例图片转换为OpenVINOTensor对于初次使用OpenVINO作为WASI-NN接口后端的开发者来说,此处算是一个坑。

第一,Intel官方在其openvino-rs开源项目的示例中,使用了*.bgr文件作为推理任务的输入。这个文件实际上是一个二进制文件,而bgr表示文件数据对应的是BGR格式的图片。另外,在openvino-rs项目中可以找到一个名为openvino-tensor-converter的工具。这个工具就是用来生成示例中的*.bgr文件。我们示例项目中的image-preprocessor也是基于这个工具改进而来的。

第二个容易出错的地方是输入tensor的维度排布。在Intel官方的openvino-model-zoo和openvino-notebooks开源项目的文档中,均使用了NCHW作为输入tensor的维度排布;并且在使用PythonAPI和RustAPI验证时,也遵从这样的维度排布。但是,使用`wasi-nncrate`[12]时,输入tensor的维度排布则是HWC。出现这种情况的具体原因暂时还不确定。

image-preprocessor和openvino-road-segmentation-adas-0001这两个子项目都是Rust项目,没有把它们整合到一个项目里的原因在于,前者依赖opencv-rs,导致无法编译为wasm模块。目前一个值得尝试的解决办法是将opencv-rs替换为image,感兴趣的同学可以尝试替换一下。

任务3:wasm推理模块

因为示例的侧重点是WASI-NN接口,所以对image-preprocessor这个部分就不进行过多的介绍,感兴趣的同学可以详细看一下代码,应该很快就能理解。那么,现在我们就来看一下WASI-NN接口。WebAssembly/wasi-nn代码库中提供了两个比较重要的文档,一个是,一个是。前者使用`wit`语法格式[13]描述了WASI-NN接口规范所涉及的接口及相关数据结构,而后者则是针对前者中所涉及的数据类型给出了更为明确的定义。下面是给出的五个接口函数:

//第一步:加载本次推理任务所需要的模型文件和配置//builder:需要加载的模型文件//encoding:后端推理引擎的类型,比如openvino,tensorflow等//target:所采用的硬件加速器类型,比如cpu,gpu等load:function(builder:graph-builder-array,encoding:graph-encoding,target:execution-target)-expectedgraph,error//第二步:通过第一步创建的graph,初始化本次推理任务的执行环境。//graph-execution-context实际上是对后端推理引擎针对本次推理任务所创建的一个session的封装,主要的作用就是将第一步中创建的graph和第三步中提供//的tensor进行绑定,以便在第四步执行推理任务中使用。init-execution-context:function(graph:graph)-expectedgraph-execution-context,error//第三步:设置本次推理任务的输入。set-input:function(ctx:graph-execution-context,index:u32,tensor:tensor)-expectedunit,error//第四步:执行本次推理任务compute:function(ctx:graph-execution-context)-expectedunit,error//第五步:推理任务成功结束后,提取推理结果数据。get-output:function(ctx:graph-execution-context,index:u32)-expectedtensor,error

从上面的注释部分可以看出,这五个接口函数构成了使用WASI-NN接口完成一次推理任务的模板。因为上述提及的两份wit格式文件只是给出了WASI-NN接口的“形式化”定义,因此每种编程语言可以再进一步实例化这些接口。在Rust语言社区,Intel的两位工程师AndrewBrown[14]和BrianJones[15]共同创建了WASI-NN的Rustbinding:wasi-nncrate。我们的示例会通过这个crate提供的接口来构建推理模块。

接下来,我们看一下本示例中用于构建推理Wasm模块的openvino-road-segmentation-adas-0001子项目。下面的代码片段是这个项目中最主要的部分:推理函数。

//openvino-road-segmentation-adas-0001/src/.///Doinferencefninfer(xml_bytes:implAsRef[u8],weights:implAsRef[u8],in_tensor:nn::Tensor,)-ResultVecf32,Boxdynstd::error::Error{//第一步:加载本次推理任务所需要的模型文件和配置letgraph=unsafe{wasi_nn::load([xml__ref(),_ref()],wasi_nn::GRAPH_ENCODING_OPENVINO,wasi_nn::EXECUTION_TARGET_CPU,).unwrap()};//第二步:通过第一步创建的graph,初始化本次推理任务的执行环境letcontext=unsafe{wasi_nn::init_execution_context(graph).unwrap()};//第三步:设置本次推理任务的输入unsafe{wasi_nn::set_input(context,0,in_tensor).unwrap();}//第四步:执行本次推理任务unsafe{wasi_nn::compute(context).unwrap();}//第五步:推理任务成功结束后,提取推理结果数据letmutoutput_buffer=vec![0f32;1*4*512*896];letbytes_written=unsafe{wasi_nn::get_output(context,0,mutoutput_buffer[..]as*mut[f32]as*mutu8,(output_()*4).try_into().unwrap(),).unwrap()};println!("bytes_written:{:?}",bytes_written);Ok(output_buffer)}

从infer函数体的代码逻辑可以发现:

接口函数的调用逻辑完全复刻了之前描述的WASI-NN接口调用模板。

目前wasi-nncrate提供的依然是unsafe接口。对于WasmEdgeRuntime社区,在现有wasi-nncrate的基础上提供一个安全封装的crate,对于社区开发者来说,使用起来会更为友好。

因为我们的示例是准备通过WasmEdgeRuntime提供的命令行接口来执行,所以我们就将infer函数所在的openvino-road-segmentation-adas-0001子项目编译为wasm模块。开始编译前,请通过下面的命令确定rustup工具链是否安装了wasm32-wasitarget。

rustuptargetlist

如果在返回结果中没有看到wasm32-wasi(installed)字样,则可以通过下面的命令安装:

rustuptargetaddwasm32-wasi

现在可以执行下面的命令,编译获得推理Wasm模块:

//确保当前目录为openvino-road-segmentation-adas-0001子项目的根目录cargobuild--target=wasm32-wasi--release

如果编译成功,在./target/wasm32-wasi/release路径下,可以找到名为的模块,即负责调用WASI-NN接口的Wasm模块。

任务4:执行wasm模块

根据模块的入口函数,通过WasmEdgeRuntime命令行接口调用该模块时,需要提供三个输入(见下面代码段中的注释):

//openvino-road-segmentation-adas-0001/src/()-Result(),Boxdynstd::error::Error{letargs:VecString=env::args().collect();//openvino模型架构文件letmodel_xml_name:str=args[1];//openvino模型权重文件letmodel_bin_name:str=args[2];//由图片转换得到的openvinotensor文件lettensor_name:str=args[3];}

为了方便复现,可以在示例项目中找到示例所需的文件:

Road-segmentation-adas-0001模型架构文件:model/

Road-segmentation-adas-0001模型权重文件:model/

推理所用的输入:tensor/

由于我们借用了Intel官方openvinomodelzoo中的模型,所以有关模型的输入、输出等信息可以在road-segmentation-adas-0001模型页面[16]找到。此外,图片文件并不能直接作为输入,而是需要经过一些预处理,比如resize和RGB转BGR等,之后再转换为字节序列,如此才能通过wasi-nncrate提供的接口传递给后端的推理引擎。上面的*.tensor文件就是图片文件image/empty_road_经过image-preprocessor工具预处理后导出的二进制文件。如果你想推理过程中尝试使用自己的图像,那么可以通过下面提供的两种方式获得相应的*.tensor文件:

//进入image-preprocessor子项目的根目录,执行以下命令cargorun----image../image/empty_road_512//或者,通过编译image-preprocessor子项目得到im2tensor可执行文件,再执行转换cargobuild--releasecd./target/releaseim2tensor--image../image/empty_road_512

在输入文件准备好后,我们就可以通过WasmEdgeRuntime提供的命令行工具来执行推理任务。

首先,确认WasmEdgeRuntime的命令行工具已经部署到本地系统:wasmedge--version

//或者

/your/local/path/to/wasmedge-release/bin/wasmedge--version
如果你没有看到-71-ge920d6e6或者类似的版本信息,那么你可以按照WasmEdgeRuntime官方安装指南[17]上的步骤完成安装。

如果WasmEdgeRuntime命令行工具能够正确工作,那么就可以执行下面的命令执行推理任务://在本示例项目的根目录下执行以下命令

wasmedge--dir.:./path/to//model//model//tensor/
推理任务开始执行后,在终端上应该会打印如下信息:LoadgraphXML,sizeinbytes:401509
Loadgraphweights,sizeinbytes:737192
Loadinputtensor,sizeinbytes:5505024
Loadedgraphintowasi-nnwithID:0
Createdwasi-nnexecutioncontextwithID:0
Executedgraphinference
bytes_written:7340032
dumptensorto""---推理任务完成后,结果数据保存在该二进制文件中
Thesizeofbytes:7340032---------------------------------------结果数据的字节数

任务5:推理任务的数据可视化

为了便于以更直观的方式观察推理过程前后的数据,我们使用JupyterNotebook来搭建一个简单的数据可视化工具。下面的三幅图片是对三个部分数据的可视化结果:中间的Segmentation图片是来自于推理Wasm模块,左右两幅分别是原始图片、最终结果图片。关于数据可视化相关的代码定义在visualize_inference_,感兴趣的同学可以作为参考改写成自己需要的样子,这部分就不进行过多的介绍了。从数据可视化方面来看,Python生态圈提供的功能性、便利性要远好于Rust生态圈。

总结

本文通过一个简单的例子,展示了如何使用WasmEdgeRuntime提供的WASI-NN接口,构建一个道路分割的机器学习示例。

从这个示例中,我们可以观察到,与传统机器学习的方法相比,基于WebAssembly技术构建机器学习应用所增加的代码规模非常有限、增加的额外代码维护成本也很低。但是,在应用方面,这些小幅增加的“成本”却可以帮助获得更佳的服务性能。比如,在云服务的环境下,WebAssembly可以提供比docker快100倍的冷启动速度,执行的持续时间少10%~50%,极低的存储空间。

WASI-NN提案提供了统一的、标准化的接口规范,使得WebAssembly运行时能够通过单一接口与多种类型的机器学习推理引擎后端进行整合,大大降低了系统集成复杂度和后期维护、升级的成本;同时,这一接口规范也提供了一种抽象,将前、后端的细节对彼此进行了隔离,从而有利于快速构建机器学习应用。随着WASI-NN接口规范的不断完善以及周边生态的逐步建立,相信WebAssembly技术将会以一种质的方式,改变当前机器学习解决方案的部署和应用方式。

WasmEdge-WASINN-examples/·second-state/WasmEdge-WASINN-examples·GitHub

相关阅读:

支持wasi-nn与wasi-crypto等Wasm提案

参考资料

[1]

完美的serverless函数:

[2]

WasmEdgeRuntime:

[3]

WASI-NN:

[4]

TensorFlow:

[5]

PyTorch:

[6]

WasmEdge-WASINN-examples:

[7]

:

[8]

openvino-model-zoo:

[9]

dokerhub:

[10]

Evcxr:

[11]

WasmEdge-WASINN-examples/openvino-road-segmentation-adas:

[12]

wasi-nncrate:

[13]

wit语法格式:

[14]

AndrewBrown:

[15]

BrianJones:

[16]

road-segmentation-adas-0001模型页面:

[17]

WasmEdgeRuntime官方安装指南:

[18]

WasmEdge-WASINN-examples:

最新文章