Web 推理是什么
Web 推理
深度学习的飞速发展,能力越来越强大,在 Web 端随着浏览器的的特性不断升级,在其上运行算法模型也慢慢从摸索尝试走向实用。Web 端的推理可以认为是“端智能“的一种,传统的机器学习由于模型大小、机器算力等限制大都是放在服务端做的,但是随着以手机为代表的端设备的性能提高以及模型架构的进化,模型逐步能够部署到端上运行。
相较于服务器部署或者云端部署的方案,Web 端上运行模型推理有这些优势:
- 用户体验:即时性带来更好的用户体验
- 效率:Web 带来更便利的集成部署和多端支持
- 成本:端计算带来更低的服务成本
- 本地化:本地计算隐私安全,支持弱网离线场景
但是 Web 端推理的挑战也很大:
- 设备资源限制:web本身的算力和存储有限,不能做大规模高强度的持续计算
- 模型限制:能够在 web 上运行的算法模型规模有限
此外在实际场景中,深度学习模型通常通过 PyTorch、TensorFlow 等框架来完成,但直接通过这些模型来进行推理效率并不高,特别是对延时要求严格的线上场景。由此,经过工业界和学术界数年的探索,模型部署有了一条流行的流水线:
这一条流水线解决了模型部署中的两大问题:使用对接深度学习框架和推理引擎的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。
Web 推理属于应用中的一环,我们更关注推理引擎这部分,推理引擎则又需要先简单了解下 Web 的算力基础。
Web 算力基础
在 web 后上执行计算按照 CPU 和 GPU 进行分类,大致有以下这些方案:
类型 | 概述 | 性能 | 兼容性 | |
---|---|---|---|---|
原生JS | CPU | 用原生JS去实现计算 | 不适合大规模计算 | 语言迁移成本高 |
WebAssembly | CPU | 跨浏览器的二进制格式 | 跨语言的便利性:可作为静态类型的高级语言编写的程序的编译目标 | 具体的运行依赖不同版本浏览器对 WebGL、Web Worker 这些特性的支持。 |
WebAssembly-SIMD | CPU | SIMD 是一种特殊的 CPU 指令,使用单条指令同时处理多个数据。 | 数据级并行优化手段。非常适用于对大量数据进行相同操作的计算任务 | chrome从 v91版本开始支持对WASM SIMD 指令的解析。 |
WebAssembly-Threads | CPU | 通过宿主环境的 WebWorker 特性来实现线程的创建管理 | 多线程并行计算 | 由于SharedArrayBuffer在浏览器的支持程度,兼容性存在问题 |
WebGL | GPU | 提供 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形能力。 | 初步利用 GPU 进行并行计算 | 本意是支持图形渲染。WebGL 1.0 在浏览器中的覆盖度已经几乎完备,但WebGL2.0 的支持程度很弱 |
WebGPU | GPU | GPU 硬件向 Web 开放的低级应用程序接口,包括图形和计算两方面 | 支持了 ComputeShader 带来了计算性能的大幅提升。基本是作为下一代的GPU计算标准 | 兼容性上浏览器都还在实验阶段 |
从性能上来说,从高到低的排序通常是 WebGPU > WebGL > WebAssembly-Threads > WebAssembly > JS,当然在部分模型中,WebAssembly-Threads 的性能表现可能持平或者优于 WebGL;
从兼容性上来说,从高到低的排序是 JS > WebGL > WebAssembly > WebAssembly-Threads > WebGPU;
Web 推理引擎
Web 推理除了算力基础的支持,还依赖对应生态下的推理引擎的支持。这些推理引擎主要负责加载相应的模型文件,并输入数据进行推理计算,一些框架不仅提供了推理的能力,也提供了模型训练上的支持。
目前大部分推理引擎对 Web算力的利用上,都集中在 WebAssembly 和 WebGL 的支持,仅有 Tensorflow.js、WebDNN和 TVM 提供了对 WebGPU 的试验性支持。
由 Google 推出,提供 Tensorflow 生态内的模型训练和推理。支持 GPU 硬件加速。在 Node.js 环境中,如果有 CUDA 环境支持,或者在浏览器环境中,有 WebGL 环境支持,TensorFlow.js 可以使用硬件进行加速。
ONNX(Open Neural Network Exchange),是微软、Facebook、亚马逊等提出用来表示深度学习模型的开放格式。所谓开放就是 ONNX 定义了一组和环境、平台均无关的标准格式,来增强各种机器学习模型的可交互性ONNX 的理念是通过统一的算子集 (Op Set)来对不同框架产生的模型进行表示。
ONNX 基本支持了主流深度学习的框架:
ONNX Runtime 是将统一的 onnx 模型包运行起来,对 ONNX 模型进行解读、优化、运行。
ONNX Runtime 支持多种运行后端包括 CPU,GPU,TensorRT,DML等。
ONNX Runtime Web是微软推出的 ONNX 模型的 Web 推理库,它支持 wasm 和 webgl 的推理。之前旧版本是 onnx.js,但目前已经全部迁移到 ONNX Runtime Web
Apache MXNet 是一个开源深度学习软件框架,被亚马逊选为 AWS 的首选深度学习框架,并受部分研究机构的支持。
mxnet.js 是对应的 web 推理库,也是由对应的 C++ 库通过 emscripten 编译的 wasm 库,但是 git 已经 6 年没有更新了。
- mnn.js
淘系 MNN 团队推出的支持 .mnn 模型的 web 推理库,mnn 本身在移动端有着良好表现,mnn.js 则完全使用 js 重写,支持 wasm 和 webgl 的推理,目前处于维护中的状态。详细介绍
- ant-tfjs
蚂蚁团队推出的基于 tfjs 优化后的推理库,兼容了 onnx 模型输入,目前聚焦在小程序环境下的适配优化,详细介绍。
WebDNN 是东京大学机器智能实验室开发的,提供了一种编译成中间格式的模型优化手段,也支持相对更多的机器学习框架。WebDNN 更聚焦于性能,支持用于GPU执行的WebGPU,以及用于CPU执行的WebAssembly。
TVM 是一个端到端的机器学习编译框架,它的目标是优化机器学习模型让其高效运行在不同的硬件平台上,使得模型推理性能达到最优的。
在 Web 上,TVM 支持支持直接编译不同的框架下的模型到 wasm+js 库,在 0.6 版本及以前是通过 OpenGL + Emscripten 的兼容来通过 WebGL 加速,在 0.7 版本开始废弃了 WebGL 支持转向 WebGPU,在 0.8 版本中 WebGPU 仍作为试验功能。
对比下 Tensorflow.js ONNX.js 和 WebDNN 推理引擎的性能:
CPU-JS 计算 | WebAssembly | WebGL(GPU) |
---|---|---|
在纯 JS 环境下进行计算,仅仅做一次分类都要 1500ms 以上。设想一下如果一个相机需要实时对拍摄的物体做分类预测(比如预测拍摄的对象是猫还是狗),那么每预测一次需要1500ms,性能很差。 | 在 WebAssembbly 环境下,性能最佳的 ONNX.js 达到了 135ms 的性能,也就是 7fps 左右,已经到了勉强能用的程度了。而 Tensorflow.js 却是糟糕的1501ms。这里是因为ONNX.js 利用了 worker 进行多线程加速,所以性能最好。 | 在 GPU 环境,可以看到Tensorflow.js 和 ONNX.js 的性能都达到了比较好的性能水平,而WebDNN 表现较为糟糕。 |
ONNX Runtime
基本概念
基于 ONNX 开放式模型格式,ONNX Runtime 的目的是让不同的机器学习和深度学习框架(如 PyTorch、TensorFlow 和 scikit-learn)训练出的模型能够无缝地部署到生产中。ONNX Runtime 的主要特点有:
- 跨平台支持:包括 Windows、Linux 和 macOS,同时也支持多种硬件架构,如 x86、ARM 和 GPU。
- 高性能:针对多种硬件进行了优化,目的是在不同设备上提供低延迟和高吞吐量。
- 灵活性:可以轻松集成到多种应用和服务中,包括云服务、边缘设备和嵌入式系统。
- 框架兼容性:ONNX Runtime 支持通过各种深度学习框架创建的模型,这些模型可以转换为 ONNX 格式并在 ONNX Runtime 中执行。
- 硬件加速器支持:支持 NVIDIA 的 CUDA、TensorRT、AMD 的 ROCm、Intel 的 MKL-DNN/OpenVINO 等硬件加速器,以优化模型的推理性能。
- 社区支持:由于微软的背书以及社区贡献,ONNX Runtime 拥有一个活跃的开发者社区,不断更新和改进。
- 训练:除了高性能的推理能力,ONNX Runtime 从1.2 以后也开始支持训练,并且能加速训练。
ONNX Runtime 支持多种编程语言:
- C++:ONNX Runtime 原生就是用 C++编写的,如果应用对性能敏感,可以直接调用。
- Python:Python API 允许加载 ONNX 模型,可以在不同的计算设备(如CPU, GPU)上运行模型,是被使用最多的语言。
- C#:C#的API,使 .NET开发者能够在应用程序中轻松地集成和使用ONNX 模型。
- JavaScript:JavaScript库允许在浏览器和 Node.js 环境中运行ONNX模型,使得在Web应用程序中部署机器学习模型变得更加容易。
另外还有 Java WinRT Objective-C Ruby Julia 等语言的支持,覆盖面非常广。
ONNX Runtime Javascript
ONNX Runtime 提供了三种适用于不同环境的 Javascript 的包:
- 浏览器:使用 onnxruntime-web 进行推理。
onnxruntime-web 可以选择使用 WebGL 或者 WebGPU 进行 GPU 处理,或者使用 WebAssembly 进行 CPU 处理。WASM 支持所有 ONNX 运算符,但 WebGL 和 WebGPU 目前仅支持一部分。
WebAssembly 可以通过配置,选择使用 wasm、wasm-threaded、wasm-simd、wasm-simd-threaded 四种方式进行推理。
onnxruntime-web 的兼容性如下图:
- Node:使用 onnxruntime-node 进行推理
在浏览器上进行推理会有些限制,比如模型对硬件规格要求高、或者不希望将模型下载到端上时,可以使用这种方式。也可以在 elctron 应用程序的后端使用 onnxruntime-node 包。
- ReactNative:使用 onnxruntime-react-native 进行推理
onnxruntime-react-native 是针对 React Native 应用开发的 ONNX Runtime 绑定。这个绑定能够实现在移动设备上运行机器学习模型。开发者可以将预训练的机器学习模型集成到他们的移动应用中,并在设备本地执行模型推理,而无需依赖外部服务器。目前对对 Android 和 iOS 都是支持的。
ONNX Runtime API
使用 ONNX Runtime 的一般步骤如下:
- 模型转换:使用相应的深度学习框架将训练好的模型导出为 ONNX 格式。
- 模型加载:在 ONNX Runtime 中加载转换好的 ONNX 模型。
- 数据准备:准备输入数据并将其转换为适合模型的格式。
- 模型推理:使用 ONNX Runtime 执行模型推理,并获取输出结果。
- 结果处理:处理模型推理的输出结果,如分类、回归或其他类型的机器学习任务。
1、InferenceSession
InferenceSession 是 ONNX Runtime 库中的一个核心类,用于运行 ONNX 模型的推理。它封装了加载模型、配置会话选项、运行推理以及管理模型输入和输出的所有细节。InferenceSession 提供了高效、方便的接口,使得进行模型推理变得简单,无需关心底层硬件或执行提供者的复杂性。
2、Tensor
在 ONNX Runtime 中,Tensor 是一种基本的数据结构,用来表示模型的输入、输出以及各层之间传递的数据。它是一个多维数组,类似于 NumPy 中的 ndarray 或 PyTorch 中的 tensor。Tensor 可以包含不同类型的数值数据,如整数、浮点数等,并且可以有不同的形状,即不同的维度和大小。
3、Env
Env 用来表示 ONNX Runtime 的全局状态和配置。这个环境对象包含了日志、线程池以及其他全局设置,通常在执行 ONNX 模型前,通常第一步就是创建一个环境实例。创建多个环境可能会导致资源浪费和性能下降。
- 日志记录:环境对象负责配置和控制 ONNX Runtime 的日志记录行为。
- 线程池和并行计算:环境可以用来配置全局线程池的大小和行为,这对于优化模型的执行性能非常重要。
- 资源管理:环境在ONNX Runtime内部用于管理全局资源,确保资源的正确分配和释放。
4、TrainingSession
ONNX Runtime 主要用于模型推理,但仍然提供了一个有限的实验性功能集 ONNX Runtime Training ,用于执行如微调等训练任务。通过扩展 ONNX Runtime 推理引擎来支持训练运算符和梯度计算实现训练优化。ONNX Runtime Training 主要用于在大规模分布式环境中进行模型的微调和优化。这些特性可能仍然是实验性的,并非主流用途。
ONNX Runtime 三个运行阶段
ONNX Runtime 在处理机器学习模型时可以分为三个阶段,Session 构造,模型加载与初始化和推理。
1、Session 构造
创建一个InferenceSession 对象,并初始化 Session 的各个成员,包括负责操作内核管理的对象,负责 Session 配置信息的对象,负责图分割和优化的对象、负责 log 管理的对象等。
2、模型加载与初始化
- 加载 ONNX 模型到 InferenceSession 中
- 解析模型中的图数据结构,包括节点和边,以及模型的参数和权重
图优化,减少计算量和内存使用,提高模型运行效率,主要优化有:
- 常量折叠:计算图中的常量表达式,并将其替换为具体的值。
- 操作符融合:将多个操作符融合成一个复合操作符,减少运行时的操作次数。
- 消除无用的节点:移除图中不影响输出的节点。
- 布局优化:改变数据的存储布局以提高存取效率。
3、模型推理
ONNX Runtime 根据优化后的模型图执行计算任务,处理输入数据,并产生输出结果。推理过程可能会利用特定硬件加速器来提高计算效率。ONNX Runtime 支持多种硬件平台和优化路径,包括 CPU、CUDA(GPU)、TensorRT 等,保证在不同的设备和环境中都能获得优异的性能。
下图是 sam.decoder.onnx 模型的网络结构,可以看到非常复杂,但实际使用推理效果速度却非常快(毫秒级别),足以证明 ONNX 优秀的推理性能。
onnxruntime-web 的使用
快速开始
下载
# 下载稳定版本
npm install onnxruntime-web
# 下载最新版本
npm install onnxruntime-web@dev
引入
// ES6 import 引入
import * as ort from 'onnxruntime-web'
// Common JS 引入
const ort = require('onnxruntime-web');
如果想使用 WebGPU 来推理
// ES6 import 引入
import * as ort from 'onnxruntime-web/webgpu';
// Common JS 引入
const ort = require('onnxruntime-web/webgpu');
跑一个简单模型
我们有一个非常简单的模型,网络结构如下,实现了两个矩阵的相乘。模型下载:model.onnx
使用的过程比较简单,流程就是前述的三步:构建 InfrenceSession、加载模型、喂输入、得到推理结果。
1、构建 InferenceSession,并加载模型
import * as ort from 'onnxruntime-web';
const session = await ort.InferenceSession.create('./model.onnx');
2、创建输入模型的数据向量
const dataA = Float32Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
const dataB = Float32Array.from([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]);
const tensorA = new ort.Tensor('float32', dataA, [3, 4]);
const tensorB = new ort.Tensor('float32', dataB, [4, 3]);
3、按照模型的输入数据结构喂给模型,执行推理
const feeds = { a: tensorA, b: tensorB };
const results = await session.run(feeds);
4、得到推理结果向量
const dataC = results.c.data;
document.write(`data of result tensor 'c': ${dataC}`);
C的结果是一个 3x3 向量:
上述例子使用的模型很简单,我们可以看看官方提供的demo,在浏览器实现 MNIST手写数据识别、MobileNet 图像分类等:
Webpack 配置
如果不是通过 script 标签引入,那么在使用 onnxruntime-web 的过程中,需要注意 webpack 配置的修改。因为在进行浏览器推理依赖于 WebAssembly,推理引擎需要加载 .wasm 文件,而 onnxruntime-web 将这些静态文件也都放在了 node\_modules 中,如果正常打包是不会被放到输出文件夹中的,因此可以借助 Webpack 插件 CopyPlugin 将 .wasm 文件一并输出:
const CopyPlugin = require("copy-webpack-plugin");
module.exports = () => {
return {
entry: 'xxxx',
output: 'xxxx',
plugins: [new CopyPlugin({
// Use copy plugin to copy *.wasm to output folder.
patterns: [{ from: 'node_modules/onnxruntime-web/dist/*.wasm', to: '[name][ext]' }]
})],
}
};
在 Webpack 中配置的输出文件的路径都是相对于 publicPath 而言的,但是推理引擎内部在判断路径时,默认是会根据当前域名的地址进行解析。
假设我们在本地开发,似乎都能请求,没有什么问题:
但是发布到线上之后,却发现路径解析并不是我们所期望的:
因为不同工程的开发环境约定不一样,有些前端工程构建的资源地址会放在域名下,有些则不是。而 onnxruntime-web 在解析 WebAssembly 文件时,默认使用了相对路径,因此是基于当前域名来解析。因此需要配置解析路径。
通过查阅文档,onnxruntime-web 提供了环境配置 Env,包含了 WebAssembly、WebGL、 WebGPU 的配置可以设置的内容有:
interface Env {
/** log级别 默认 warning */
logLevel?: 'verbose' | 'info' | 'warning' | 'error' | 'fatal';
/** 是否开启 debug 模式,默认 false */
debug?: boolean;
/** WebAssembly 配置 */
wasm: Env.WebAssemblyFlags;
/** WebGL 配置 */
webgl: Env.WebGLFlags;
/** WebGPU 配置*/
webgpu: Env.WebGpuFlags;
}
interface WebAssemblyFlags {
/** 线程数 默认为0 用于并行计算 */
numThreads?: number;
/** 是否开启 SIMD 默认true */
simd?: boolean;
/** 初始化超时时间 默认0 */
initTimeout?: number;
/** 自定义 .wasm 文件路径,如果覆盖必须问绝对路径 */
wasmPaths?: WasmPrefixOrFilePaths;
/** 主线程与字进行是否开启代理 默认 false*/
proxy?: boolean;
}
type WasmPrefixOrFilePaths = string | {
'ort-wasm.wasm'?: string;
'ort-wasm-threaded.wasm'?: string;
'ort-wasm-simd.wasm'?: string;
'ort-wasm-simd-threaded.wasm'?: string;
};
wasmPaths 就是我们需要设置的东西。此外还需要注意在本地开发时,需要设置为相对路径
import * as ort from 'onnxruntime-web';
const isLocal = process.env.NODE_ENV !== 'production';
ort.env.wasm.wasmPaths = {
'ort-wasm.wasm': isLocal ? 'static/ort-wasm.wasm' : 'https://dev.g.alicdn.com/xxxx/0.0.0/static/ort-wasm.wasm',
'ort-wasm-threaded.wasm': isLocal ? 'static/ort-wasm-threaded.wasm' : 'https://dev.g.alicdn.com/xxxx/static/ort-wasm-threaded.wasm',
'ort-wasm-simd.wasm': isLocal ? 'static/ort-wasm-simd.wasm' : 'https://dev.g.alicdn.com/xxxx/static/ort-wasm-simd.wasm',
'ort-wasm-simd-threaded.wasm': isLocal ? 'static/ort-wasm-simd-threaded.wasm' : 'https://dev.g.alicdn.com/msd/taji-studio/xxxxx/ort-wasm-simd-threaded.wasm',
}
上述流程确实比较繁琐,且如果是基于 onnxruntime-web 进行物料包开发,那么即使在物料包里解决了静态资源路径的问题,在项目工程中使用时仍然会遇到同样的问题(有点像死循环),所以确实更推荐 <script> 标签引入的方式,能够规避路径解析的问题。
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script>
这样加载的文件地址都是确定匹配的。
总结
ONNX Runtime 的通用设计理念就决定了它非常适合于 Web 应用,也让我们看到了未来会有更多复杂的 AI 应用在浏览器直接运行,提供更强的计算能力和交互体验。
作者:ES2049 / Timeless
文章可随意转载,但请保留此 原文链接。
非常欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com 。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。