作者:Michael Yuan,WasmEdge Maintainer
Vercel 是开发和托管 Jamstack 应用程序的当先平台。与传统 Web 应用程序在 runtime 从服务器动静生成 UI 不同,Jamstack 应用程序由动态 UI(HTML 和 JavaScript)和一组通过 JavaScript 反对动静 UI 元素的 serverless 函数组成。
Jamstack 的形式有很多益处。这其中最重要的益处之一是其弱小的性能。因为 UI 不再从核心服务器的 runtime 生成,因而服务器上的负载要少得多,咱们能够通过边缘网络(例如 CDN)部署 UI。
然而,边缘 CDN 只解决了散发动态 UI 文件的问题。后端的 serverless 函数可能依然很慢。事实上,目前风行的 serverless 平台存在家喻户晓的性能问题,例如冷启动迟缓,对于交互式应用程序尤其如此。在这方面,WebAssembly 大有可为。
应用 WasmEdge,一个 CNCF 托管的云原生的 WebAssembly runtime , 开发者能够编写高性能 serverless 函数,部署在公共云或边缘计算节点上。本文中,咱们将摸索如何应用 Rust 编写的 WasmEdge 函数来驱动 Vercel 应用程序后端。
为什么在 Vercel Serverless 应用 WebAssembly ?
Vercel 平台曾经有了十分易于应用的 serverless 框架,能够部署 Vercel 中托管的函数。正如下面探讨的,应用 WebAssembly 和 WasmEdge 是为了 进一步提高性能。用 C/C++、Rust 和 Swift 写的高性能函数能够轻松编译成 WebAssembly。这些 WebAssembly 函数比 serverless 函数中罕用的 JavaScript 或 Python 快得多。
那么问题来了,如果原始性能是惟一的指标,为什么不间接将这些函数编译为机器本地可执行文件呢?这是因为 WebAssembly“容器”依然提供许多有价值的服务。
首先,WebAssembly 在 runtime 层级隔离了函数。代码中的谬误或内存平安问题不会流传到 WebAssembly runtime 之外。随着软件供应链日渐变得更加简单,将代码在容器中运行,以避免他人未经受权通过依赖库拜访您的数据,这点十分重要。
其次,WebAssembly 字节码是 可移植的。开发者只需构建一次,无需放心将来底层 Vercel serverless 容器(操作系统和硬件)的扭转或更新。它还容许开发者在类似的托管环境中重复使用雷同的 WebAssembly 函数,如腾讯 Serverless Functions 的私有云中,或者在像 YoMo 这样的数据流框架中。
最初,WasmEdge Tensorflow API 提供了最合乎 Rust 标准的、执行 Tensorflow 模型的形式。WasmEdge 装置了 Tensorflow 依赖库的正确组合,并为开发者提供了对立的 API。
概念和解释说了很多,趁热打铁,让咱们看看示例应用程序!
筹备工作
因为咱们的 demo WebAssembly 函数是用 Rust 编写的,因而您须要装置有 Rust 编译器。确保按如下形式装置 wasm32-wasi
编译器指标,以生成 WebAssembly 字节码。
$ rustup target add wasm32-wasi
Demo 应用程序的前端是用 Next.js 编写的,并部署在 Vercel 上。咱们假如您曾经具备应用 Vercel 的基本知识。
示例 1: 图片解决
咱们的第一个 demo 应用程序是让用户上传图片,而后调用 serverless 函数将其变成黑白图片。在开始之前,你能够试一下这个部署在 Vercel 上的 demo。
首先 fork demo 应用程序的 GitHub repo。在 Vercel 上部署应用程序,只需从 Vercel for GitHub 页面点 GitHub repo 导入。
此 GitHub repo 的内容是一个 Vercel 平台的规范 Next.js 应用程序。其后端 serverless 函数在 api/functions/image_grayscale
文件夹中。src/main.rs
文件蕴含 Rust 程序的源代码。Rust 程序从 STDIN
读取图片数据,而后将黑白图片输入到 STDOUT
。
use hex;
use std::io::{self, Read};
use image::{ImageOutputFormat, ImageFormat};
fn main() {let mut buf = Vec::new();
io::stdin().read_to_end(&mut buf).unwrap();
let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap();
let img = image::load_from_memory(&buf).unwrap();
let filtered = img.grayscale();
let mut buf = vec![];
match image_format_detected {
ImageFormat::Gif => {filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap();},
_ => {filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap();},
};
io::stdout().write_all(&buf).unwrap();
io::stdout().flush().unwrap();}
应用 Rust 的 cargo
工具构建 Rust 程序为 WebAssembly 字节码或者原生代码。
$ cd api/functions/image-grayscale/
$ cargo build --release --target wasm32-wasi
将 build artifacts 复制到 api
文件夹。
$ cp target/wasm32-wasi/release/grayscale.wasm ../../
Vercel 在设置 serverless 环境时会运行
api/pre.sh
。这时会装置 WasmEdge runtime,而后将 WebAssembly 字节码程序编译为一个本地的so
库,从而更快地执行。
api/hello.js
文件合乎 Vercel 的 serverless 标准。它加载 WasmEdge runtime,在 WasmEdge 中启动已编译的 WebAssembly 程序,并通过 STDIN 传递上传的图像数据。留神这里 api/hello.js
运行由 api/pre.sh
生成的已编译的 grayscale.so
文件从而失去更好的性能。
const fs = require('fs');
const {spawn} = require('child_process');
const path = require('path');
module.exports = (req, res) => {
const wasmedge = spawn(path.join(__dirname, 'WasmEdge-0.8.1-Linux/bin/wasmedge'),
[path.join(__dirname, 'grayscale.so')]);
let d = [];
wasmedge.stdout.on('data', (data) => {d.push(data);
});
wasmedge.on('close', (code) => {let buf = Buffer.concat(d);
res.setHeader('Content-Type', req.headers['image-type']);
res.send(buf);
});
wasmedge.stdin.write(req.body);
wasmedge.stdin.end('');
}
这样就实现了。接下来将 repo 部署到 Vercel,就能够失去一个 Jamstack 应用程序。该应用程序具备高性能的基于 Rust 和 WebAssembly 的 serverless 后端。
示例 2: AI 推理
第二个 demo 应用程序是让用户上传图像,而后调用 serverless 函数来辨认图片中的次要物体。
它与上一个示例在同一个 GitHub repo,然而在 tensorflow
分支。留神:将此 GitHub repo 导入到 Vercel 网站上时,Vercel 将为每个分支创立一个预览 URL。tensorflow
分支将会有本人的部署 URL。
用于图像分类的后端 serverless 函数位于 tensorflow
分支中的 api/functions/image-classification
文件夹中。src/main.rs
文件蕴含 Rust 程序的源代码。Rust 程序从 STDIN
读取图像数据,而后将文本输入输入到 STDOUT
。它用 WasmEdge Tensorflow API 来运行 AI 推理。
pub fn main() {
// Step 1: Load the TFLite model
let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite");
let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt");
// Step 2: Read image from STDIN
let mut buf = Vec::new();
io::stdin().read_to_end(&mut buf).unwrap();
// Step 3: Resize the input image for the tensorflow model
let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224);
// Step 4: AI inference
let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite);
session.add_input("input", &flat_img, &[1, 224, 224, 3])
.run();
let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1");
// Step 5: Find the food label that responds to the highest probability in res_vec
// ... ...
let mut label_lines = labels.lines();
for _i in 0..max_index {label_lines.next();
}
// Step 6: Generate the output text
let class_name = label_lines.next().unwrap().to_string();
if max_value > 50 {println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name);
} else {println!("It does not appears to be any food item in the picture.");
}
}
应用 cargo
工具将 Rust 程序构建为 WebAssembly 字节码或原生代码。
$ cd api/functions/image-grayscale/
$ cargo build --release --target wasm32-wasi
将 build artifacts 复制到 api
文件夹
$ cp target/wasm32-wasi/release/classify.wasm ../../
同样,api/pre.sh
脚本在此应用程序中装置 WasmEdge runtime 及其 Tensorflow 依赖项。它还在部署时将 classify.wasm
字节码程序编译为 classify.so
原生共享库。
api/hello.js
文件合乎 Vercel serverless 标准。它加载 WasmEdge runtime,在 WasmEdge 中启动已编译的 WebAssembly 程序,并通过 STDIN
传递上传的图像数据。留神 api/hello.js
运行由 api/pre.sh
产生的编译过的 classify.so
文件,以达到更好的性能。
const fs = require('fs');
const {spawn} = require('child_process');
const path = require('path');
module.exports = (req, res) => {
const wasmedge = spawn(path.join(__dirname, 'wasmedge-tensorflow-lite'),
[path.join(__dirname, 'classify.so')],
{env: {'LD_LIBRARY_PATH': __dirname}}
);
let d = [];
wasmedge.stdout.on('data', (data) => {d.push(data);
});
wasmedge.on('close', (code) => {res.setHeader('Content-Type', `text/plain`);
res.send(d.join(''));
});
wasmedge.stdin.write(req.body);
wasmedge.stdin.end('');
}
当初能够将 forked 的 repo 部署到 vercel,就会失去一个辨认物体的 Jamstack 利用。
只需更改模板里的 Rust 函数,你就能够部署本人的高性能 Jamstack 利用了!
瞻望
从 Vercel 以后的 serverless 容器运行 WasmEdge 是一种向 Vercel 应用程序增加高性能函数的简略办法。
如果你用 WasmEdge 开发了乏味的 Vercel 函数或者利用,能够增加微信 h0923xw,就能够支付一份小礼品。
展望未来,更好的办法是将 WasmEdge 自身作为容器应用,而不是明天这样用 Docker 和 Node.js 来启动 WasmEdge。这样,咱们能够以更高效率运行 serverless 函数。WasmEdge 曾经与 Docker 工具兼容。如果有趣味退出 WasmEdge 和 CNCF 进行这项激动人心的工作,欢送退出咱们的 channel。