# 在 Transformers.js 中实验提议的跨源存储 API

- 来源：Hugging Face：Blog（RSS）
- 发布时间：2026-06-23 08:00
- AIHOT 分数：64
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmqqz7cqg0ezbslp5zr85m87l
- 原文链接：https://huggingface.co/blog/cross-origin-storage

## 精选理由

这个Chrome提案让不同网站的AI模型共享缓存，对用Transformers.js的Web开发者是切实的性能改进，但还只是早期实验。

## AI 摘要

Transformers.js 在浏览器中运行 AI 模型时，不同来源的 Web 应用会重复下载并缓存相同的模型资源（如 Xenova/whisper-tiny.en）和 Wasm 运行时文件（如 4,733 kB 的 ort-wasm-simd-threaded.asyncify.wasm），即使资源 URL 相同，浏览器因 Network Isolation Key 隔离缓存，单次 demo 就产生 177 MB 冗余下载和存储。Cross-Origin Storage API 是一项早期提案，旨在让跨来源应用共享缓存的模型和运行时资源。目前该 API 尚未在浏览器原生实现，但可通过 Chrome 扩展注入 polyfill 进行实验。

## 正文

Transformers.js 为 Web 开发者提供了一种简单的方式，通过任务特定的管道（pipeline）在其 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));

两个完全不同的大模型，但它们都依赖于同一个 4,733 kB 的 ort-wasm-simd-threaded.asyncify.wasm WebAssembly（Wasm）运行时文件，该文件来自 Transformers.js 所基于的底层 ONNX Runtime 库。在不同源上打开扩展演示，你会在网络标签页中注意到，Wasm 运行时也会被重新下载并再次缓存。

因此，即使你运行的应用不共享相同的大模型，你的浏览器仍然会对你已经拥有的共享 Wasm 资源发出冗余请求，并且还会再次缓存它们，从而占用硬盘空间。

缓存隔离

大模型资源服务

默认情况下，大模型资源来自 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 作为键之外，还使用网络隔离键作为键。网络隔离键由顶级站点和当前框架站点组成。以前面托管在源 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://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

https://rawcdn.rawgit.net

https://cdn.jsdelivr.net/npm/onnxruntime-web@1.26.0-dev.20260416-b7804b056c/dist/ort-wasm-simd-threaded.asyncify.wasm

因此，即使资源 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);

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 特意模仿了文件系统标准中的 `FileSystemDirectoryHandle.getFileHandle()`，你可能从源私有文件系统（OPFS）API 中熟悉它。哈希参数扮演的角色与 OPFS 中的名称参数相同：唯一标识一个资源。`options.create` 标志的工作方式也相同：缺失或为 `false` 表示只读访问，为 `true` 表示你打算写入。

控制谁能读取什么

并非每个资源都应在全局共享。COS 通过存储文件时的 `origins` 选项，让开发者能够精确控制可见性。

设置 `origins: '*'` 会使文件全局可用。任何源都可以通过哈希找到它。这对于 AI 模型资源或 Transformers.js 示例中的 Wasm 运行时来说是正确选择：其核心意义在于，Web 上的每个应用都能受益于单个缓存副本。

传递一个特定的源列表，例如 `origins: ['https://write.example.com', 'https://calculate.example.com']`，会将访问权限限制在这些站点。这对于在公司自有资产之间共享、且不应被其他任何人发现的专有资源非常适用，例如商业办公套件中使用的专有校对 AI 模型。

完全省略来源会使文件仅对同站来源可用。对于组织内所有子域共享的资源而言，这是一个合理的默认设置，但不应跨越组织边界使用。

一条重要规则：可见性可以提升，但绝不能降低。如果某个文件已全局可用，后续试图以受限来源列表存储该文件的操作将被静默忽略。这可以防止恶意行为者重新存储公共资源并缩小其可用范围。反向操作是可行的：最初以受限来源列表存储的文件，后续可以放宽访问权限。任何站点（不限于原始存储者）都可以针对同一哈希值调用 `requestFileHandle()`（哈希值并非秘密），并传入 `create: true` 和更宽泛的 origins 值，只要浏览器验证哈希值匹配，该资源从此便对更广泛的受众可用。请注意，提升权限的站点仍必须通过返回的句柄写入完整文件。此要求旨在防止站点利用权限提升路径作为侧信道，来检测特定文件是否已存储在 COS 中。

设计上的完整性

COS 一个微妙但重要的特性是，浏览器在写入文件时会验证哈希值。如果写入的数据与声明的哈希值不匹配，写入操作将失败并报错。这使得完整性检查成为自动行为：从 COS 读取文件的应用程序可以确信它获取的正是预期的字节内容。这与通过网络下载后自行计算哈希值所能获得的保证相同。

这在 Transformers.js 场景中具有双重价值。目前，大多数应用程序在下载模型权重后，实际上无法验证 CDN 是否提供了正确的字节内容。而使用 COS，存储中的每个文件在写入时都会隐式完成验证，无论文件来自何处——无论是官方的 Hugging Face CDN 还是某个随机站点的自建镜像。

不牺牲实用性的隐私保护

当然，跨源共享缓存也提出了与分区 HTTP 缓存相反的问题：如果任何网站都可以通过哈希值来探测某个文件是否存在，那么攻击者是否可以通过检查某个游戏引擎的 Wasm 模块是否被缓存，来了解用户的浏览历史呢？

COS 通过两种互补机制来解决这个问题：

首先，`origins` 字段：不应被全局探测的专有资源，只需避免使用 `origins: '*'` 来存储即可。通过开发者教育，鼓励开发者在合理的情况下考虑这一点。

其次，可用性门控：即使是全局声明的文件，如果该文件尚未在足够多的不同源上被访问过，浏览器也可能抑制对该文件存在性的确认。一个只出现在一两个网站上的文件，仍可能被用作跨站标识符，因此浏览器可能会返回一个错误，仿佛该文件根本不存在，无论磁盘上实际存储了什么。在 Chrome 团队，我们意识到不常见资源可能造成的隐私泄露，并计划通过限制哪些具体资源可以被缓存来普遍缓解这一问题。具体的缓解措施仍在完善中。

关键在于，这意味着错误并非一个确定的答案。它可能意味着“未存储”，也可能意味着“已存储，但浏览器不告诉你”。应用应始终以相同方式处理：回退到网络请求。

这对 Transformers.js 示例意味着什么

回到之前的玩具示例：`ort-wasm-simd-threaded.asyncify.wasm` 运行时体积为 4,733 kB，所有基于 Transformers.js 的应用无论使用哪种 AI 模型，都会共享该文件。使用 COS 后，第一个加载该文件的应用会将其下载一次，并以 SHA-256 哈希值作为键、`origins: '*'` 为权限存储起来。后续任何应用，无论来自 `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。Pull Request #1549 引入了一个实验性的 COS 缓存后端，通过一个 opt-in 标志启用。在设置 pipeline 之前，只需一行代码即可开启：

import { env, pipeline } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0";

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 供后续调用者使用。以玩具示例来说，实际优势在于 `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 Cache API）。

Transformers.js 并非唯一在尝试 COS 的库。WebLLM（可选启用，详见文档）和 wllama（自动启用，参见 PR）同样对这一提议中的 API 感到兴奋。

在 Chrome 团队中，我们正在考虑在浏览器中原生实现 COS API。作为一项早期阶段的提案，我们欢迎各方就 API 本身以及提案的形态提供反馈。Cross-Origin Storage 仓库是提交问题、表达支持或发起 PR 的地方。

本文提及的模型 2

transformers.js

javascript

2026 年 4 月 23 日

公告

transformers.js

transformers

Transformers.js v4：现已登陆 NPM！

2026 年 2 月 9 日
