Experimenting with the proposed Cross-Origin Storage API in Transformers.js
TL;DR · AI 摘要
跨域存储 API 可减少浏览器中模型资源的重复下载和缓存,提升性能。
核心要点
- 使用跨域存储 API 可减少 177 MB 的重复下载和存储。
- Transformers.js 默认使用 Xenova/whisper-tiny.en 模型进行语音识别。
- ONNX Runtime 的 Wasm 运行时文件可被多个模型共享。
结构提纲
按章节快速跳转。
- §引言
介绍 Transformers.js 在浏览器中使用 AI 模型的现状和挑战。
浏览器中模型资源的重复下载和缓存导致性能下降。
多个模型依赖相同的 Wasm 运行时文件,可优化存储。
跨域存储 API 可减少重复下载,提升浏览器性能。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 跨域存储 API 的潜力
- 模型资源缓存问题
- 重复下载 177 MB
- Xenova/whisper-tiny.en 模型
- Wasm 运行时资源共享
- ort-wasm-simd-threaded.asyncify.wasm
金句 / Highlights
值得收藏与分享的关键句。
使用跨域存储 API 可减少 177 MB 的重复下载和存储。
Transformers.js 默认使用 Xenova/whisper-tiny.en 模型进行语音识别。
多个模型依赖相同的 Wasm 运行时文件,可优化存储。
在 Transformers.js 中尝试提议的跨源存储 API
返回文章
[-1
]
[0
发布于 2026 年 6 月 23 日
GitHub 上的更新
点赞
1
[
Thomas Steiner
tomayac
关注
(这是 Google Chrome 团队的 Developer Relations 工程师 Thomas Steiner 的客座文章。)
Transformers.js 为 Web 开发者提供了一种简单的方式,通过任务特定的管道在他们的 Web 应用中使用 transformer 的强大功能。为了在浏览器中运行推理,开发者会创建一个 pipeline() 的实例,并指定他们希望使用该管道的任务。作为一个具体的例子,以下代码片段展示了如何设置一个自动语音识别(ASR)管道。
import
{ pipeline }
from
'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0'
;
const
asr =
await
pipeline
(
'automatic-speech-recognition'
,
'Xenova/whisper-tiny.en'
,
{
device
:
'webgpu'
},
);
const
result =
await
asr
(
'jfk.wav'
);
console
.
log
(result);缓存挑战
你将在源代码中注意到,我指定了 Xenova/whisper-tiny.en 作为模型,这在常见的英文自动语音识别任务中是一个非常好的选择。事实上,根据 Transformers.js 默认模型解析的相关摘录,它甚至还是默认模型。
模型资源
当你在浏览器中运行这个示例时,Transformers.js 会自动处理相关模型资源和 Wasm 文件的下载和缓存。下图显示了访问该应用后 Chrome DevTools 缓存存储部分的情况。当你重新加载页面时,资源会从缓存 API 提供,模型几乎可以立即返回结果。
然而,Xenova/whisper-tiny.en 是一个非常受欢迎的模型(如前所述,它甚至还是 Transformers.js 中的 ASR 默认模型),你可以想象,不仅仅是你访问的一个应用会使用它。为了模拟这种情况,这里是之前相同的示例应用,但来自不同的源。当你访问这个不同源的应用时,浏览器需要重新下载和缓存所有模型资源,即使它们与之前逐字节完全相同。即使在这个玩具示例中,这也会导致 177 MB 的重复下载和存储,你可以在 Chrome DevTools 应用程序面板的存储部分查看。你可以想象,这会迅速累积起来。
Wasm 运行时资源
但情况变得更糟。让我们在玩具示例中添加一个第二个管道:情感分析。情感分析默认使用 Xenova/distilbert-base-uncased-finetuned-sst-2-english 模型。如果不指定模型,Transformers.js 的默认模型解析会自动为你选择它。
const
classifier =
await
pipeline
(
'sentiment-analysis'
);
const
sentiment =
await
classifier
(result.
text
);
pre.
append
(
'\n\n'
+
JSON
.
stringify
(sentiment,
null
,
2
));两个完全不同的 AI 模型,但它们依赖于 Transformers.js 基于的 ONNX Runtime 库中的相同 4,733 kB ort-wasm-simd-threaded.asyncify.wasm WebAssembly(Wasm)运行时文件。在不同的源上打开扩展演示,你将在网络标签中注意到,Wasm 运行时也会被重新下载和缓存。
即使你运行的应用程序不共享相同的 AI 模型,浏览器仍然会为已经拥有的共享 Wasm 资源发出冗余请求,并且还会再次缓存这些资源,这会占用你硬盘上的空间。
缓存隔离
#### AI 模型资源的提供
默认情况下,AI 模型资源来自 Hugging Face Hub,并最终通过 Hugging Face CDN 提供。浏览器会请求一个资源,例如 https://huggingface.co/Xenova/distilbert-base-uncased-finetuned-sst-2-english/resolve/main/config.json,然后该请求会被重定向到最终的 CDN URL,例如 https://huggingface.co/api/resolve-cache/models/Xenova/distilbert-base-uncased-finetuned-sst-2-english/0b6928efcb76139cae2c6881d49cda67fe119f42/config.json?%2FXenova%2Fdistilbert-base-uncased-finetuned-sst-2-english%2Fresolve%2Fmain%2Fconfig.json=&etag=%223c36342ef1f74de2797d667c68c6b7b988d0b87c%22。
#### Wasm 运行时资源的提供
默认情况下,Wasm 运行时资源由 jsDelivr CDN 提供。例如,在撰写本文时,ort-wasm-simd-threaded.asyncify.wasm 来自 https://cdn.jsdelivr.net/npm/onnxruntime-web@1.26.0-dev.20260416-b7804b056c/dist/ort-wasm-simd-threaded.asyncify.wasm。
现在你可能会说,如果不同的应用,即使运行在不同的源上,最终都从相同的 CDN URL 提供资源,只要最终的 URL 相同,缓存不应该成为问题。不幸的是,浏览器长期以来的缓存机制并不是这样工作的。文章《通过隔离缓存提高安全性和隐私性》详细介绍了所有细节,但本质上,缓存是按源隔离的,以防止时序攻击:网站响应 HTTP 请求所需的时间可能会暴露浏览器之前是否访问过相同的资源,这使得浏览器容易受到安全和隐私泄露的威胁。
#### Chrome 的实现
具体的实现可能因浏览器而异,但在 Chrome 中,缓存资源除了使用资源 URL 作为键之外,还会使用网络隔离键(Network Isolation Key)。网络隔离键由顶级站点和当前帧站点组成。以之前在 https://googlechrome.github.io 和 https://rawcdn.rawgit.net 上托管的玩具示例为例。如果它们都使用来自 https://cdn.jsdelivr.net/npm/onnxruntime-web@1.26.0-dev.20260416-b7804b056c/dist/ort-wasm-simd-threaded.asyncify.wasm 的 Wasm 运行时,它们的缓存键将如下表所示。
网络隔离键 | 资源 URL | 顶级站点 | 当前帧站点 --- | --- | --- | --- https://googlechrome.github.io | https://cdn.jsdelivr.net/npm/onnxruntime-web@1.26.0-dev.20260416-b7804b056c/dist/ort-wasm-simd-threaded.asyncify.wasm | https://rawcdn.rawgit.net |
即使资源 URL 完全相同,由于网络隔离键不匹配,也不会发生缓存命中,这意味着重复下载和重复存储。这就是跨域存储提案旨在解决的挑战。
引入跨域存储 API
💡 注意:跨域存储 API 是一个早期阶段的提案,尚未最终确定。虽然该提案的 API 尚未在任何浏览器中原生实现,但你不需要等待就可以进行实验。安装跨域存储扩展,可以在所有页面上注入
navigator.crossOriginStorage的 polyfill 并测试完整的流程。
所提出的跨源存储(COS)API 引入了一个专用的 navigator.crossOriginStorage 接口,通过该接口,Web 应用可以在跨源边界存储和检索大文件,文件的标识不是通过 URL,而是通过密码学哈希。
关于密码学哈希的最后一点非常重要。由于 COS 通过文件的哈希而不是 URL 或源来标识文件,因此你在访问 https://googlechrome.github.io 时下载的 ort-wasm-simd-threaded.asyncify.wasm Wasm 运行时,与 https://rawcdn.rawgit.net 即将请求的相同运行时会被视为相同的文件,无论这两个源是从哪里获取的。下面的代码片段展示了基本流程。
const
hash = {
algorithm
:
'SHA-256'
,
value
:
'8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4'
,
};
try
{
const
handle =
await
navigator.
crossOriginStorage
.
requestFileHandle
(hash);
// 缓存命中!通过 getFile() 获取文件作为 Blob 并直接使用。
const
fileBlob =
await
handle.
getFile
();
}
catch
(err) {
// 缓存未命中。从网络下载,然后存储以备下次使用。
const
fileBlob =
await
fetch
(
'https://cdn.jsdelivr.net/.../ort-wasm-simd-threaded.asyncify.wasm'
)
.
then
(
r
=>
r.
blob
());
const
handle =
await
navigator.
crossOriginStorage
.
requestFileHandle
(
hash,
{
create
:
true
,
origins
:
'*'
},
);
const
writableStream =
await
handle.
createWritable
();
await
writableStream.
write
(fileBlob);
await
writableStream.
close
();
}如果资源存在于 COS 中,你将获得一个 FileSystemFileHandle,你可以通过 getFile() 直接读取 Blob(生成的 File 继承自 Blob)。如果资源不在 COS 中,你将回退到网络,然后将资源写入 COS,以便下次需要它的应用程序使用,这可能是你的应用程序,也可能是另一个完全不相关的应用程序。
该 API 的设计是基于你可能熟悉于 Origin Private File System(OPFS)API 的 File System Standard 的 FileSystemDirectoryHandle.getFileHandle()。哈希参数在 COS 中扮演与 OPFS 中名称参数相同的角色:唯一标识一个资源。选项中的 create 标志也以相同的方式工作:当为只读访问时,缺省或为 false;当你打算写入时,设置为 true。
控制谁可以读取什么
并非所有资源都应该被全局共享。COS 通过存储文件时的 origins 选项,为开发者提供了对可见性的精确控制。
- 设置 origins: '*' 使文件对所有源都可用。任何源都可以通过哈希找到该文件。这对于 AI 模型资源或 Transformers.js 示例中的 Wasm 运行时来说是正确的选择:其目的就是让 Web 上的每个应用程序都能从单个缓存副本中受益。
- 传递一个具体的来源列表,例如 origins: ['https://write.example.com', 'https://calculate.example.com'],将访问权限限制到这些站点。这对于在公司内部共享的专有资源非常适用,这些资源不应被其他人发现,例如在商业办公套件中使用的专有校对 AI 模型。
- 完全省略 origins 会使文件仅对同源的来源可用。这对于在组织所有子域之间共享的资源来说是一个合理的默认设置,但不打算跨越组织边界。
一个重要的规则是:可见性可以升级,但不能降级。如果一个文件已经全局可用,之后尝试用受限的来源列表存储它时,该操作将被静默忽略。这可以防止恶意行为者重新存储一个公开资源并限制其可用性。反过来是可以的:一个最初用受限来源列表存储的文件,之后可以变得更加宽松。任何网站,而不仅仅是原始存储者,都可以通过相同的哈希值(哈希值不是秘密)调用 requestFileHandle(),并设置 create: true 和更广泛的来源值。只要浏览器验证哈希匹配,资源从此对更广泛的受众开放。请注意,升级的网站仍必须通过返回的句柄写入完整的文件。这一要求的存在是为了防止网站利用升级路径作为侧信道,检测特定文件是否已存储在 COS 中。
通过设计实现完整性
COS 有一个微妙但重要的特性:当浏览器写入文件时,会验证哈希值。如果你写入的数据与声明的哈希值不匹配,写入操作将失败并返回错误。这使得完整性检查变得自动:从 COS 读取文件的应用可以确信它获得的正是预期的字节。这种保证与它在网络下载后自行计算哈希值时所拥有的保证是一样的。
这在 Transformers.js 的场景中尤其有用。目前,在下载模型权重后,大多数应用没有实际的方法来验证 CDN 是否提供了正确的字节。有了 COS,存储库中的每个文件在写入时都会隐式地进行验证,无论其来源是官方的 Hugging Face CDN,还是某个随机网站的自托管镜像。
在不牺牲实用性的情况下保护隐私
当然,跨源共享缓存也提出了与分区 HTTP 缓存相反的问题:如果任何网站都可以通过哈希值探测文件是否存在,攻击者是否可以通过检查某种文件(例如游戏引擎的 Wasm 模块)是否被缓存,来了解用户的浏览历史?
COS 通过两种互补的机制来解决这个问题:
- 首先,来源字段:不应被全局探测的专有资源不应使用
origins: '*'进行存储。通过开发者教育,开发者被鼓励在合适的时候考虑这一点。
- 其次,可用性控制:即使对于全局声明的文件,如果浏览器尚未在足够多的不同来源中遇到该文件,它可能会抑制文件存在性的确认。一个只出现在一两个网站上的文件仍可能作为跨站标识符,因此浏览器可能会返回一个错误,就像该文件根本不存在一样,无论磁盘上实际存储了什么。在 Chrome 团队,我们意识到不常见的资源可能导致隐私泄露,并计划通过限制哪些具体资源可以被缓存来缓解这一问题。具体的缓解措施仍在完善中。
关键的是,这意味着错误并不是一个明确的答案。它可能意味着“未存储”,也可能意味着“已存储,但浏览器不会告诉你”。应用应始终以相同的方式处理这种情况:回退到网络。
回到之前的玩具示例:ort-wasm-simd-threaded.asyncify.wasm 运行时的大小为 4,733 kB,所有使用 Transformers.js 的应用程序都会共享它,无论使用的是哪种 AI 模型。通过 COS,第一个加载它的应用程序会下载一次,并将其存储在 SHA-256 哈希值下,且来源为 '*'。之后的所有应用程序,无论是位于 https://googlechrome.github.io、https://rawcdn.rawgit.net,还是其他任何来源,都能立即在 COS 中找到它。至于 177 MB 的重复 Whisper 模型权重?同样的情况:Xenova/whisper-tiny.en 会下载一次,第二次通过哈希识别,然后从 COS 中以毫秒级的速度提供服务。当然,Xenova/distilbert-base-uncased-finetuned-sst-2-english 也是如此。
Transformers.js 本身已经在库级别试点使用 COS API。拉取请求 #1549 引入了一个实验性的 COS 缓存后端,该后端通过一个可选标志启用。启用它只需要在设置管道之前添加一行代码:
import
{ env, pipeline }
from
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0"
;
// 👇 启用实验性的 Cross-Origin Storage 缓存后端。
env.
experimental_useCrossOriginStorage
=
true
;
const
asr =
await
pipeline
(
'automatic-speech-recognition'
,
'Xenova/whisper-tiny.en'
, {
device
:
'webgpu'
});
const
result =
await
asr
(
'jfk.wav'
);
console
.
log
(result);设置该标志后,Transformers.js 会通过获取原始 Xet 指针(示例原始指针文件)并提取其 oid sha256: 字段,来解析每个 Xet 追踪模型文件(大型 ONNX 权重文件)的 SHA-256 哈希值。然后,它会使用该哈希值作为 navigator.crossOriginStorage 的键。如果模型已经在 COS 中(因为其他网站已先将其存储在 COS 中),则可以立即提供服务,而无需进行网络往返。如果没有,则会回退到常规下载,并将结果存储在 COS 中,供下一个调用者使用。在玩具示例中,实际的优势在于 Xenova/whisper-tiny.en 和 Xenova/distilbert-base-uncased-finetuned-sst-2-english(当然还有 ort-wasm-simd-threaded.asyncify.wasm)无论有多少个不同的来源请求它们,只需穿越一次网络即可。
注意标志上的 experimental_ 前缀。这是有意为之,表明底层的浏览器 API 尚未标准化,可能会在不进行主要版本更新的情况下发生变化。
今天就来试试
目前还没有任何浏览器原生实现了 COS API,但你不需要等待就可以开始尝试。安装 Cross-Origin Storage 扩展,它会在所有页面上注入 navigator.crossOriginStorage 的 polyfill,从而测试完整的流程。你可以查看该扩展的源代码并按照使用说明开始使用。
安装扩展后,您可以立即体验完整的端到端流程:打开第一个启用 COS 的玩具示例,加载 Xenova/whisper-tiny.en 模型,然后从第二个源打开启用 COS 的玩具示例。与之前看到的 177 MB 重新下载不同,模型将从 COS 在毫秒内加载。当您打开扩展的弹出窗口时,可以看到 COS 正在运行。如果您通过资源查看,可以看到具有 SHA-256 哈希值 950978b1dbcbf250335358c1236053ba19a7f7849b33dc777f4421b72b7626fa 的资源在 https://googlechrome.github.io 和 https://rawcdn.rawgit.net 之间共享。这可能并不明显,但您可以通过比较 Hugging Face 上的 SHA-256 哈希值来验证,您看到的是 https://huggingface.co/Xenova/whisper-tiny.en/blob/main/onnx/decoder_model_merged.onnx。目前,该扩展主要面向像您这样的高级用户。一旦在浏览器中实现,浏览器的设置页面将提供更友好的集成方式。下面的截图显示了扩展的弹出窗口,其中“按资源查看”标签处于活动状态,您可以看到共享的资源及其哈希值,以及拥有该资源的两个源的 COS 缓存。
行动号召
如果您正在构建自己的 Transformers.js 应用程序,行动号召很简单:在您的第一个 pipeline() 调用之前添加 env.experimental_useCrossOriginStorage = true,安装扩展,并观察网络标签页中的重复下载消失。每个选择加入的网站都会使其他网站用户的体验更快、更便宜。选择加入是完全无风险的:如果由于用户没有安装 COS 扩展,COS API 不被支持,代码将回退到默认路径(Web 缓存 API)。
Transformers.js 并不是唯一在尝试使用 COS 的项目。WebLLM(可选加入,参见文档)和 wllama(自动,参见 PR)同样对这个提议的 API 感到兴奋。
在 Chrome 团队,我们正在考虑在浏览器中原生实现 COS API。作为一个初步的提案,我们欢迎对 API 本身以及提案的结构提出反馈意见。Cross-Origin Storage 仓库是提交问题、表达支持或提交 PR 的地方。
本文提到的模型 2
更多来自我们博客的文章
guide
transformers.js
javascript
如何在 Chrome 扩展中使用 Transformers.js
39
2026 年 4 月 23 日
announcement
transformers
Transformers.js v4:现已在 NPM 上发布!
96
2026 年 2 月 9 日
社区
编辑
预览
通过拖拽到文本输入框、粘贴或
点击此处
上传图片、音频和视频。
轻点或粘贴此处上传图片
评论
· 注册或登录以发表评论