前言
SegmentAnything Model (SAM) 是 Meta 开源的分割万物 AI 模型,因笔者主要使用 Vue,因此对官方 Demo 进行了工程迁移工作,这里记录了迁移过程遇到的一些问题。
没有使用多线程加速
官方的 React Demo 项目使用ort-wasm-simd-threaded.wasm
进行多线程加速。
而笔者 Vue 工程的 Demo 却使用的是ort-wasm-simd.wasm
,并没有多线程加速,卡住了渲染进程。
断点ort-wasm-simd.wasm
的 Initiator 流程,发现在wasm-factory.ts
中isMultiThreadSupported()
返回值为false
,进而知道是typeof SharedArrayBuffer === 'undefined'
的原因,导致的多线程加速没有开启的问题。
查看 MDN 了解到,浏览器做了安全限制,需要通过站点 HTTP 消息头让文档处于安全上下文,以启用SharedArrayBuffer
。
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
没有官方 Demo 的流畅性
官方 Demo 的逻辑很简单,每次鼠标移动的时候对指定目标点进行图像分割,伪代码大概如下:
image.onmousemove = _.throttle((e) => {
runONNX(e);
}, 15)
但是同样的代码逻辑,官方的 Demo 能保证渲染的完整性,而笔者的 Vue 工程在移动鼠标时却阻塞了渲染,停止鼠标移动才进行渲染。
起初笔者以为又漏写了什么,但笔者对比下来,唯一的区别是
React:
import { useEffect } from 'react';
useEffect(() => {
runONNX();
}, [clicks]);
Vue:
import { watch } from 'vue';
watch([clicks], () => {
runONNX();
});
笔者起初认为这是一样的逻辑,并未察觉有何不妥。
查阅 React useEffect和Vue watchEffect 了解到:
对于 React,默认情况下,effect 将在每轮渲染结束后执行。
查阅 Vue.js 文档 了解到:
对于 Vue,默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。
因此,笔者此时以为只是简单的时机问题,做出了以下改动:
import { watch } from 'vue';
watch([clicks], () => {
runONNX();
}, {
flush: 'post',
});
发现毫无变化。
此时笔者意识到,useEffect 难道有什么过人之处?随后查阅了【译】useEffect究竟何时执行?,文中有一段吸引了我的注意React给出了保证 ———— 每次更新都不会被错过
,那么 React 是如何保证每次更新都不会错过的呢。
答案就是 Fiber 架构。
可以在 Fiber vs Stack Demo 页面直观的体验到 Fiber 架构的优越性,至于 Fiber 架构本文就不展开了,鉴于 requestIdleCallback
的兼容性,React 官方是通过 MessageChannel
来实现的。
所以在本文的 Vue Demo 中,简单地通过 requestIdleCallback
在 Chrome 浏览器就能体验到官方 Demo 相同的效果:
import { watch } from 'vue';
watch([clicks], () => {
requestIdleCallback(() => {
runONNX();
});
});