Experimenting 成为 AI 产业观察信号
Experimenting with the proposed Cross-Origin Storage API in Transformers.js: Transformers.js provides Web developers with a simple way to use the power of transformers in their Web apps through task-specific pipelines. T

正文
Transformers.js 为 Web 开发者提供了一种简单方式,可通过任务专用的流水线在 Web 应用中使用 transformer 的能力。要在浏览器中运行推理,开发者只需创建一个 `pipeline()` 实例,并指定希望该流水线处理的任务。下面这个具体示例展示了如何搭建一个自动语音识别(ASR)流水线。
你会在源代码中注意到,我指定了 `Xenova/whisper-tiny.en` 作为模型;对于常见的英文自动语音识别任务来说,这确实是个相当不错的选择。事实上,根据 Transformers.js 的默认模型解析,正如所链接的摘录所示,它甚至还是默认模型。
当你在浏览器中运行这个示例时,Transformers.js 会自动负责下载并缓存相关的模型资源和 Wasm 文件。下面的截图展示了访问应用后 Chrome DevTools 的 Cache storage 部分。重新加载页面时,这些资源会通过 Cache API 提供,模型几乎会立刻返回结果。
不过,`Xenova/whisper-tiny.en` 作为一个热门模型——而且正如前面提到的,它甚至还是 Transformers.js 中 ASR 的默认模型——你完全可以想象,不只一个你访问的应用会用到它。为了模拟这种情况,下面把前面的示例应用放在另一个不同的 origin 下提供。当你访问这个不同 origin 的应用时,浏览器不能像之前那样几乎立刻使用,而是必须再次下载并缓存全部模型资源,即使这些字节和之前完全一样。即便在这个玩具示例里,这也会带来 177 MB 的重复下载和存储,你可以在 Chrome DevTools 的 Application 面板里的 Storage 部分查看。这种开销会迅速累积。
但情况还会更糟。让我们在这个玩具示例里再加一个第二个管线:情感分析。情感分析默认使用 Xenova/distilbert-base-uncased-finetuned-sst-2-english 模型。由于没有显式指定模型,Transformers.js 的默认模型解析会自动为你选中它。
这是两个完全不同的 AI 模型,但它们都依赖于底层 ONNX Runtime 库中的同一个 4,733 kB `ort-wasm-simd-threaded.asyncify.wasm` WebAssembly(Wasm)运行时文件,而 Transformers.js 正是建立在这个库之上的。打开不同 origin 上的扩展示例,你会在 Network 标签页里注意到,这个 Wasm 运行时也会再次被下载并缓存。
因此,即使你运行的应用并不共享相同的 AI 模型,浏览器仍然会对你已经拥有的共享 Wasm 资源发起冗余请求,并且还会再次缓存它们,这会占用你的硬盘空间。
默认情况下,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 运行时资源由 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 。
你可能会说,如果不同的应用即使运行在不同的 origin 上,最终都从同一个 CDN URL 提供资源,那么只要最终 URL 相同,缓存就不应该是问题。不幸的是,浏览器中的缓存机制长期以来并不是这样工作的。文章《Gaining security and privacy by partitioning the cache》对此有详细说明,但核心原理是:缓存会按 origin 进行隔离,以防止时序攻击——网站响应 HTTP 请求所花费的时间,可能会暴露浏览器之前是否访问过同一资源,从而使浏览器面临安全和隐私泄露风险。
具体实现会因浏览器而异,但在 Chrome 中,缓存资源除了资源 URL 之外,还会使用 Network Isolation Key 进行键控。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 完全相同,由于 Network Isolation Key 不匹配,也不会命中缓存,这就意味着重复下载和重复存储。这正是 Cross-Origin Storage 提案要解决的问题。
💡 注:Cross-Origin Storage API 仍处于早期提案阶段,还不是最终版本。虽然该 API 目前还没有被任何浏览器原生实现,但你不必等到那时才开始尝试。安装 Cross-Origin Storage 扩展,即可在所有页面注入 `navigator.crossOriginStorage` polyfill,并测试完整流程。
提议中的 Cross-Origin Storage(COS)API 引入了一个专用的 `navigator.crossOriginStorage` 接口,Web 应用可以通过它跨 origin 边界存储和检索大文件;文件的标识不是 URL,而是密码学哈希值。
关于密码学哈希值的这一点至关重要。因为 COS 不是按 URL 或 origin,而是按哈希值来识别文件,所以你在访问 `https://googlechrome.github.io` 时下载的 `ort-wasm-simd-threaded.asyncify.wasm` Wasm runtime,会被识别为与 `https://rawcdn.rawgit.net` 即将请求的那个文件完全相同,不管这两个 origin 分别是从哪里获取它的。下面的代码片段展示了基本流程。
如果资源已经存在于 COS 中,你会得到一个 `FileSystemFileHandle`,可以通过 `getFile()` 直接读取其 blob(返回的 `File` 继承自 `Blob`)。如果资源不在 COS 中,就回退到网络,并把该资源写入 COS,供下一个需要它的应用使用,这个应用可能是你的应用,也可能是另一个完全无关的应用,甚至可能来自完全不同的 origin。
这个 API 的设计刻意借鉴了 File System Standard 中的 `FileSystemDirectoryHandle.getFileHandle()`,你大概在 Origin Private File System(OPFS)API 里已经见过。`hash` 参数扮演的角色与 OPFS 中的 `name` 参数相同:用于唯一标识一个资源。`options.create` 标志的作用也一样:省略或设为 `false` 时表示只读访问,设为 `true` 时则表示你打算写入。
并不是每个资源都应该被全局共享。COS 通过在保存文件时使用 `origins` 选项,让开发者可以精确控制可见范围。
将 `origins: '*'` 设为全局可用。任何 origin 都可以通过 hash 找到它。对于 AI 模型资源,或者 Transformers.js 示例中的 Wasm runtime,这都是合适的选择:其目的就是让 Web 上的每个应用都能受益于同一份缓存副本。
传入一个具体的 origin 列表,例如 `origins: ['https://write.example.com', 'https://calculate.example.com']`,则会将访问权限限制在这些站点。这非常适合在公司自有各个产品之间共享、但不应被其他人发现的专有资源,比如商业办公套件中使用的专有校对 AI 模型。
完全省略 `origins` 时,这个文件就只对同站点来源可用。这是一个合理的默认设置,适合在组织的所有子域之间共享资源,但并不打算跨越组织边界。
有一条重要规则:可见性只能升级,不能降级。如果某个文件已经是全局可用的,之后再试图用受限的 `origins` 列表保存它,会被静默忽略。这可以防止恶意行为者把一个公开资源重新保存后,反过来缩小它的可访问范围。反过来则可以:最初以受限 `origins` 列表保存的文件,之后可以变得更宽松。任何站点,而不只是最初保存它的站点,都可以针对同一个哈希值(哈希不是秘密)调用 `requestFileHandle()`,并传入 `create: true` 和更宽的 `origins` 值;只要浏览器验证哈希匹配,资源就会从那一刻起对更广泛的受众可用。需要注意的是,执行升级的站点仍然必须通过返回的 handle 写入完整文件。之所以有这个要求,是为了防止站点利用升级路径作为侧信道,探测某个特定文件是否已经存放在 COS 中。
COS 一个微妙但重要的特性是:浏览器在你写入文件时会验证哈希。如果你写入的数据与声明的哈希不匹配,写入就会报错失败。这使完整性检查变成自动化:从 COS 读取文件的应用可以确信,它拿到的正是自己预期的那些字节。这个保证和它在网络下载后自行计算哈希所能获得的保证是一样的。
这在 Transformers.js 的场景里尤其有用。如今,在下载模型权重之后,大多数应用都没有实用的方法来验证 CDN 是否返回了正确的字节。有了 COS,存储中的每个文件在写入时都会被隐式验证,不管它来自哪里——官方 Hugging Face CDN,还是某个随机站点自建的镜像。
当然,跨源共享缓存也会引出与分区 HTTP 缓存相反的同一个问题:如果任何网站都能通过哈希探测某个文件是否存在,那么攻击者是不是就能通过检查某个游戏引擎的 Wasm 模块是否已缓存,推断出用户的浏览历史?
COS 通过两种互补机制来应对这一点:
第一是 origins 字段:那些不应被全局探测到的专有资源,就不应以 `origins: '*'` 的形式存储;通过开发者教育,也会鼓励开发者在合适的场景下考虑这一点。
第二是可用性门控:即便是全局声明的文件,如果浏览器判断它没有在足够多的不同来源中出现,也可能会抑制对该文件存在与否的确认。一个只出现在一两个网站上的文件,仍然可能充当跨站标识符,因此浏览器可能会像文件根本不存在一样返回错误,而不管磁盘上实际上有没有这个文件。在 Chrome 团队看来,这类不常见资源可能带来的隐私泄露是需要重视的,计划总体上通过限制哪些具体资源可以被缓存来缓解这一问题。具体的缓解措施仍在进一步完善中。
关键在于,这意味着错误并不是一个确定无疑的答案。它可能表示“未存储”,也可能表示“已存储,但浏览器没有告诉你”。应用程序应始终以同样的方式处理:回退到网络。
回到前面的玩具示例:ort-wasm-simd-threaded.asyncify.wasm 运行时体积达到 4,733 kB,而且无论 Transformers.js 驱动的应用使用哪种 AI 模型,它都会被所有应用共享。借助 COS,第一个加载它的应用会先下载一次,并按其 SHA-256 hash 将其存储到 origins: '*' 下。之后的每个应用,无论是在 https://googlechrome.github.io 上、在 https://rawcdn.rawgit.net 上,还是在其他任何 origin 上,都会立即在 COS 中找到它。那 177 MB 的 Whisper 模型权重重复下载怎么办?同样的道理:Xenova/whisper-tiny.en 只会被下载一次,第二次再遇到时会通过 hash 识别出来,并在几毫秒内从 COS 提供。Xenova/distilbert-base-uncased-finetuned-sst-2-english 当然也是如此。
Transformers.js 本身已经在库层面试点使用 COS API。pull request #1549 在一个可选开关后面引入了实验性的 COS 缓存后端。启用它只需要在设置 pipeline 之前加一行:
请注意这个标志前面的 `experimental_` 前缀。这是有意为之,表示底层浏览器 API 还没有标准化,未来可能会在不提升主版本号的情况下发生变化。启用该标志后,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`)无论有多少不同的 origin 请求,都只需要跨越网络传输一次。
这个玩具示例用 Xenova/whisper-tiny.en 完全没问题,但如果用户在自己的 COS 缓存里已经有其他 Whisper 变体,当然也不妨一并利用。例如,用户可能已经有 Xenova/whisper-large-v3,顾名思义,它比 tiny 变体大得多。Transformers.js 的 Model Registry 让你可以灵活选择模型。如果你知道应用需求可以由例如 Xenova/whisper-tiny.en、whisper-medium.en 或 Xenova/whisper-large-v3 中的任意一个满足,就可以在 registry 中查找每个模型对应的文件,探测它们是否存在于 COS 缓存中——其中可能部分或完整包含你需要的模型资源——然后最终决定选用哪个模型。`ModelRegistry.is_pipeline_cached()` API 直接与 COS(当然也包括 Cache API)集成,因此这个操作非常顺手。
COS API 目前还没有在任何浏览器中原生实现,但你不必等到那一天再去尝试。安装 Cross-Origin Storage 扩展,即可在所有页面上注入 `navigator.crossOriginStorage` polyfill,并测试完整流程。查看该扩展的源代码,并按照使用说明开始上手。
装好扩展后,立刻试试完整的端到端体验:先打开启用了 COS 的第一个 toy example,让它加载 `Xenova/whisper-tiny.en`,然后再从第二个 origin 打开同样启用了 COS 的 toy example。你会看到,模型不再像之前那样重新下载 177 MB,而是几毫秒内就从 COS 提供出来。打开扩展的弹出窗口后,就能看到 COS 正在工作。如果切换到 View by Resource,可以看到 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`。目前,这个扩展主要面向像你这样的高级用户。等到浏览器原生实现之后,浏览器的 Settings 页面会提供更友好的集成。下方截图展示了扩展弹出窗口中 View by Resource 选项卡激活时的样子,你可以看到共享资源及其哈希,以及持有它的两个 origin。
如果你正在构建自己的 Transformers.js 应用,行动很简单:在首次调用 `pipeline()` 之前添加 `env.experimental_useCrossOriginStorage = true`,安装扩展,然后看着 Network 面板里的重复下载消失。每个选择启用的站点,都会让其他站点的用户获得更快、更省成本的体验。启用完全没有风险:如果因为用户没有安装 COS 扩展而不支持 COS API,代码会自动回退到默认路径(Cache API)。
Transformers.js 并不是唯一一个在尝试 COS 的项目。WebLLM(需手动启用,见文档)和 wllama(自动启用,见 PR)也同样看好这个拟议中的 API。
在 Chrome 团队这边,我们正在考虑在浏览器中原生实现 COS API。作为一个早期提案,我们欢迎大家就这个 API 以及提案本身的形式提出反馈。Cross-Origin Storage 仓库是提交 issue、表示支持,或发起 PR 的地方。
· 注册或登录以评论
AI解读
这是一条雷达解读:它可能反映 AI 产品、开发者工具、模型基础设施或研究方向的新变化。
对开发者来说,可以作为后续选题、产品观察或技术调研的线索。
建议打开原文核对细节,并观察是否有同类信号在其他来源重复出现。