技术学习分享_一航技术 技术资讯 从 WebAssembly 角度改进 WASI-NN | WASI-NN 系列文章2

从 WebAssembly 角度改进 WASI-NN | WASI-NN 系列文章2

广告位

上一篇文章中,我们展示了如何使用 OpenVINO 构建一个道路分割的机器学习推理任务。在这个过程中,我们观察到两个有趣且值得进一步完善的工作:

  • 在示例中使用到了 wasi-nn crate,其为 WASI-NN 提案提供了 Rust 接口实现,从而大大降低了使用 Rust 语言构建基于 WebAssembly 技术的机器学习任务的流程复杂度。不过,wasi-nn crate 提供的接口是 unsafe 的,更适合作为底层API 用于构建更高层的库。因此,我们可以基于 wasi-nn crate 创建一个提供 safe 接口的库。
  • 在对输入图片进行预处理的时候,我们使用到了 opencv crate 。但是,因为 opencv crate 无法编译为 wasm 模块,所以就不得不将图片预处理模块独立出来,单独作为一个项目来实现。

对于上述两个观察,我们尝试做了初步的尝试:

  • 借鉴 Rust 和 WebAssembly 社区开发者的一些尝试,我们对 wasi-nn crate 中定义的unsafe 接口进行了抽象和安全封装,构建了 wasmedge-nn crate 原型。本文的后续部分将演示如何使用 wasmedge-nn crate 替换 wasi-nn crate,重新构建上一篇文章中所使用的道路分割 Wasm 推理模块。
  • Rust 社区中著名的图像处理库之一 image crate 提供了我们所需的图片预处理的基本能力;此外,由于其是 Rust 原生实现,所以基于这个库来构建我们需要的图像处理库是可以编译为 wasm 模块的。

下面,我们继续使用道路分割示例,具体演示一下我们的改进方案。

wasmedge-nn crate 的安全接口

上一篇文章中,我们已经使用了 wasi-nn crate 中定义的五个主要的接口,他们分别对应 WASI-NN 提案中的接口。我们对照着看一下改进后的接口。下图中,蓝色框图中是我们要使用的 wasmedge-nn cratenn 模块中定义的接口,绿色框图为相对应的 wasi-nn crate 中定义的接口,箭头显示了它们之间的映射关系。关于 wasmedge-nn crate 的设计细节,感兴趣的同学可以先行阅读源码,后续我们会在另外一篇文章进行讨论,所以这里就不进行过多的阐述了。

从 WebAssembly 角度改进 WASI-NN | WASI-NN 系列文章2

基于wasmedge-nn构建wasm推理模块

接下来,我们就通过代码来展示如何使用 wasmedge-nn 提供的接口和相关数据结构,重新实现 wasm 推理模块。

下面的示例代码是使用 wasmedge-nn crate 提供的安全接口重新构建的 wasm 推理模块。通过代码中的注释,可以很容易地发现:接口的调用顺序与使用 wasi-nn 接口的调用顺序保持一致;而最明显的不同之处在于,因为 wasmedge-nn 中定义的安全接口,所以示例代码中不再有 unsafe 字样出现。正如在上一篇文章中所阐述,示例代码中所展示的接口调用顺序可以看作一个模板:如果更换一个模型来完成一个新的推理任务,下面的代码几乎不需要任何改动。感兴趣的同学可以尝试使用其它的模型来试试。下面示例的完整代码可以在这里找到。

use std::env; use wasmedge_nn::{     cv::image_to_bytes,     nn::{ctx::WasiNnCtx, Dtype, ExecutionTarget, GraphEncoding, Tensor}, };  fn main() -> Result<(), Box<dyn std::error::Error>> {     let args: Vec<String> = env::args().collect();     let model_xml_name: &str = &args[1];     let model_bin_name: &str = &args[2];     let image_name: &str = &args[3];      // 加载图片,并转换为字节序列     println!("Load image file and convert it into tensor ...");     let bytes = image_to_bytes(image_name.to_string(), 512, 896, Dtype::F32)?;   	   	// 创建 Tensor 实例,包括数据、维度、类型等信息     let tensor = Tensor {         dimensions: &[1, 3, 512, 896],         r#type: Dtype::F32.into(),         data: bytes.as_slice(),     };        // 创建 WASI-NN Context 实例     let mut ctx = WasiNnCtx::new()?;    	// 加载模型文件及其它推理过程需要的配置信息     println!("Load model files ...");     let graph_id = ctx.load(         model_xml_name,         model_bin_name,         GraphEncoding::Openvino,         ExecutionTarget::CPU,     )?;    	// 初始化执行环境     println!("initialize the execution context ...");     let exec_context_id = ctx.init_execution_context(graph_id)?; 		   	// 为执行环境提供输入     println!("Set input tensor ...");     ctx.set_input(exec_context_id, 0, tensor)?; 		   	// 执行推理计算     println!("Do inference ...");     ctx.compute(exec_context_id)?; 		   	// 获取推理计算的结果     println!("Extract result ...");     let mut out_buffer = vec![0u8; 1 * 4 * 512 * 896 * 4];     ctx.get_output(exec_context_id, 0, out_buffer.as_mut_slice())?; 		   	// 导出计算结果到指定的二进制文件     println!("Dump result ...");     dump(         "wasinn-openvino-inference-output-1x4x512x896xf32.tensor",         out_buffer.as_slice(),     )?;      Ok(()) }  

这里需要说明的是,最后导出的 .tensor 二进制文件用于后续可视化推理结果数据。由于示例代码是通过命令行来执行,在某些环境下(比如Docker)无法直接通过 API 调用展示推理结果,所以这里就只是导出推理结果。对于其他类型的推理任务,比如使用分类模型,在不需要可视化显示的情况下,就可以考虑直接打印分类结果,而无需导出到文件。作为参考,这里我们提供一段Python代码(引用自WasmEdge-WASINN-examples/openvino-road-segmentation-adas),通过读取导出的 .tensor 文件,可视化推理结果数据。

import matplotlib.pyplot as plt import numpy as np  # 读取保存推理结果的二进制文件,并将其转换为原始维度 data = np.fromfile("wasinn-openvino-inference-output-1x4x512x896xf32.tensor", dtype=np.float32) print(f"data size: {data.size}") resized_data = np.resize(data, (1,4,512,896)) print(f"resized_data: {resized_data.shape}, dtype: {resized_data.dtype}")  # 准备用于可视化的数据 segmentation_mask = np.argmax(resized_data, axis=1) print(f"segmentation_mask shape: {segmentation_mask.shape}, dtype: {segmentation_mask.dtype}")  # 绘制并显示 plt.imshow(segmentation_mask[0])  

基于 image crate 的图像预处理函数

除了提供安全的接口用于执行推理任务,通过 cv 模块,wasmedge-nn crate 提供了基本的图像预处理函数 image_to_bytes。这个函数的实现借鉴了 image2tensor 开源项目的设计,主要用于将输入图片转换为满足推理任务要求的字节序列,在后续步骤中进一步构建 Tensor 变量作为推理模块接口函数的输入。由于当前的后端仅支持 OpenVINO,图像处理的需求还比较简单,所以这个 cv 模块仅仅包含了这一个图像预处理函数。

use image::{self, io::Reader, DynamicImage};  // 将图片文件转换为特定尺寸,并转换为指定类型的字节序列 pub fn image_to_bytes(     path: impl AsRef<Path>,     nheight: u32,     nwidth: u32,     dtype: Dtype, ) -> CvResult<Vec<u8>> {   	// 读取图片     let pixels = Reader::open(path.as_ref())?.decode()?;   	// 转换为特定的尺寸     let dyn_img: DynamicImage = pixels.resize_exact(nwidth, nheight, image::imageops::Triangle);   	// 转换为BGR格式     let bgr_img = dyn_img.to_bgr8();      	// 转换为指定类型的字节序列     let raw_u8_arr: &[u8] = &bgr_img.as_raw()[..];     let u8_arr = match dtype {         Dtype::F32 => {             // Create an array to hold the f32 value of those pixels             let bytes_required = raw_u8_arr.len() * 4;             let mut u8_arr: Vec<u8> = vec![0; bytes_required];              for i in 0..raw_u8_arr.len() {                 // Read the number as a f32 and break it into u8 bytes                 let u8_f32: f32 = raw_u8_arr[i] as f32;                 let u8_bytes = u8_f32.to_ne_bytes();                  for j in 0..4 {                     u8_arr[(i * 4) + j] = u8_bytes[j];                 }             }              u8_arr         }         Dtype::U8 => raw_u8_arr.to_vec(),     };      Ok(u8_arr) }   

有了安全的 wasmedge-nn crate, 与支持将 OpenCV 编译成 Wasm 的图像处理库,使用 Rust 与 WebAssembly 进行 AI 推理就变得非常简单。接下来只需按照第一篇文章的说明运行 OpenVINO 模型就可以了。

总结

wasi-nn crate 为 Rust 开发者提供了基础性的底层接口,在使用 WasmEdge Runtime 内建的WASI-NN 支持的场景下,大大降低了接口调用的复杂性;在此基础之上,通过提供安全封装的接口,wasmedge-nn crate 进一步完善了推理任务的用户接口定义;同时,通过进一步的抽象,将面向推理任务的前端接口与面向推理引擎的后端接口进行了解耦,从而实现前、后端之间的松耦合。

此外,通过 cv 模块提供的、基于 image crate 的图像预处理函数,允许图像预处理模块和推理计算模块编译在同一个 Wasm模块中,从而实现从原始图像到推理任务的输入张量、再到推理计算、最后到计算结果导出的流水线化。

关于 wasmedge-nn crate 的细节,我们会在下一篇文章中进行详细阐述。感兴趣的同学也可以前往 wasmedge-nn GitHub repo 进一步了解。我们也欢迎对 WasmEdge + AI感兴趣的开发者和研究员反馈你们的意见和建议;同时,也欢迎将你们的实践经验和故事分享到我们的 WasmEdge-WASINN-examples 开源项目。谢谢!

本文来自网络,不代表技术学习分享_一航技术立场,转载请注明出处。

作者: 一航技术

上一篇
下一篇
广告位

发表回复

返回顶部