Experimenting with the proposed Cross-Origin Storage API in Transformers.js
웹 브라우저에서 AI 모델을 실행할 때 대용량 모델 파일과 Wasm 런타임의 중복 다운로드 문제를 해결하기 위한 '교차 출처 저장소(Cross-Origin Storage) API' 제안을 소개합니다. 암호화 해시(S
웹 브라우저에서 AI 모델을 실행할 때 대용량 모델 파일과 Wasm 런타임의 중복 다운로드 문제를 해결하기 위한 '교차 출처 저장소(Cross-Origin Storage) API' 제안을 소개합니다. 암호화 해시(SHA-256)를 기반으로 여러 사이트 간에 캐시를 안전하게 공유하여, 대용량 AI 모델을 단 한 번만 다운로드하고 모든 웹 앱에서 즉시 로드할 수 있게 해줍니다.
안녕하세요! 이번 글은 구글 크롬 팀의 개발자 관계 엔지니어(Developer Relations Engineer) 토마스 슈타이너(Thomas Steiner)가 기고한 글로, 브라우저 환경에서 AI 모델을 구동할 때 겪는 캐시 문제를 혁신적으로 해결해 줄 새로운 제안인 교차 출처 저장소(Cross-Origin Storage, 이하 COS) API에 대해 깊이 있게 다룹니다.
Transformers.js는 웹 개발자가 특정 태스크용 파이프라인을 통해 웹 브라우저에서 간편하게 트랜스포머 모델의 강력한 성능을 활용할 수 있도록 돕습니다. 브라우저에서 추론을 실행하기 위해 개발자는 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);웹 브라우저에서 AI를 실행할 때 발생하는 '캐시'의 한계
위의 코드 예시에서는 영어 음성 인식 작업에 매우 적합한 Xenova/whisper-tiny.en 모델을 지정했어요. 사실 이 모델은 Transformers.js의 기본 모델 확인 규칙에 따른 기본 ASR 모델이기도 합니다.
브라우저에서 이 코드를 실행하면, Transformers.js는 관련 모델 리소스와 WebAssembly(Wasm) 파일들을 자동으로 다운로드하고 캐싱합니다. 페이지를 새로고침하면 이러한 리소스는 Cache API를 통해 즉시 제공되므로 모델 결과가 거의 실시간으로 반환되죠.
하지만 Xenova/whisper-tiny.en은 매우 널리 쓰이는 인기 모델입니다. 여러분이 방문하는 수많은 웹 앱 중에서 이 모델을 사용하는 곳이 한두 군데가 아닐 텐데요.
만약 서로 다른 도메인(Origin)에서 구동되는 두 개의 웹 앱이 동일한 모델을 사용한다면 어떻게 될까요? 안타깝게도 브라우저는 바이트 단위까지 완벽히 동일한 모델 파일임에도 불구하고, 새로운 도메인에 방문할 때마다 수백 메가바이트에 달하는 리소스를 처음부터 다시 다운로드하고 캐싱해야 합니다. Whisper-tiny 모델의 경우 매번 177MB에 달하는 중복 다운로드와 저장 공간 낭비가 발생하는 셈이죠.
설상가상: 공유되는 Wasm 런타임의 중복 다운로드
문제는 여기서 끝나지 않아요. 감성 분석(Sentiment Analysis) 파이프라인을 추가해 보겠습니다. 감성 분석은 기본적으로 Xenova/distilbert-base-uncased-finetuned-sst-2-english 모델을 사용합니다.
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 라이브러리의 동일한 Wasm 파일(ort-wasm-simd-threaded.asyncify.wasm, 약 4,733kB)에 의존합니다. 다른 도메인에서 구동되는 웹 앱을 열면, 이 Wasm 파일마저 네트워크 탭에서 새로 다운로드되고 있는 것을 볼 수 있어요.
결국 사용자가 완전히 다른 AI 모델을 사용하는 앱들을 방문하더라도, 브라우저는 이미 하드디스크에 갖고 있는 공통 Wasm 리소스를 불필요하게 다시 요청하고 또 중복 저장하게 되는 것입니다.
캐시 분리(Cache Partitioning)라는 보안 장벽
기본적으로 AI 모델 리소스는 허깅페이스 허브(Hugging Face Hub)와 CDN을 통해 제공됩니다. 브라우저는 리소스를 요청할 때 최종적으로 고유한 CDN URL로 리다이렉트되어 다운로드합니다. Wasm 파일 또한 jsDelivr CDN을 통해 다운로드되죠.
각기 다른 웹 사이트가 결국 완벽히 똑같은 CDN URL에서 파일을 가져온다면 브라우저 캐시를 공유하면 되지 않을까요? 아쉽게도 현대 브라우저는 보안상의 이유로 그렇게 동작하지 않습니다.
브라우저는 타이밍 공격(Timing Attack)과 같은 개인정보 및 보안 유출을 막기 위해 출처(Origin)별로 캐시를 격리합니다. 특정 사이트가 HTTP 요청에 응답하는 시간을 측정해 사용자가 과거에 다른 사이트에서 어떤 리소스를 다운로드했는지 알아내는 것을 방지하기 위함이에요.
크롬(Chrome)의 경우, 캐시 리소스를 키(Key)로 등록할 때 단순히 리소스 URL만 사용하는 것이 아니라 네트워크 격리 키(Network Isolation Key)를 결합하여 관리합니다. 이 격리 키는 최상위 사이트(Top-level site)와 현재 프레임 사이트(Current-frame site) 정보로 구성됩니다.
| 최상위 사이트 (Top-level site) | 현재 프레임 사이트 (Current-frame site) | 리소스 URL |
|---|---|---|
https://googlechrome.github.io | https://googlechrome.github.io | https://cdn.jsdelivr.net/.../ort-wasm-simd-threaded.asyncify.wasm |
https://rawcdn.rawgit.net | https://rawcdn.rawgit.net | https://cdn.jsdelivr.net/.../ort-wasm-simd-threaded.asyncify.wasm |
위 표처럼 리소스 URL이 완전히 동일하더라도 네트워크 격리 키가 다르기 때문에 캐시 히트(Cache Hit)가 발생하지 않고, 결국 중복 다운로드와 중복 저장이 강제됩니다. 바로 이 난제를 해결하기 위해 등장한 것이 교차 출처 저장소(Cross-Origin Storage, COS) API입니다.
크로스 오리진 저장소(Cross-Origin Storage) API의 등장
💡 참고: 교차 출처 저장소 API는 현재 초기 제안 단계에 있는 규격입니다. 브라우저에 네이티브로 구현되어 있지는 않지만, 크롬 익스텐션 등을 통해 제공되는 폴리필(Polyfill)로 지금 즉시 실험해 볼 수 있습니다.
COS API는 navigator.crossOriginStorage 인터페이스를 제공하여, 웹 앱이 출처의 경계를 넘어 대용량 파일을 저장하고 가져올 수 있도록 합니다. 이때 리소스를 식별하는 기준은 URL이나 출처(Origin)가 아니라, 오직 파일 고유의 암호화 해시(Cryptographic Hash) 값입니다.
파일을 해시 값으로 식별하기 때문에, 어느 도메인에서 어떤 URL로 파일을 다운로드했든 간에 파일 내용이 같다면 브라우저는 동일한 리소스로 인식합니다. 기본 흐름을 보여주는 아래 코드를 살펴보세요.
const hash = {
algorithm: 'SHA-256',
value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4',
};
try {
const handle = await navigator.crossOriginStorage.requestFileHandle(hash);
// 캐시 히트! 파일 핸들을 통해 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에 저장해 둔 리소스라면 getFile()을 통해 즉시 로컬 파일 시스템에서 꺼내올 수 있고, 저장소에 없다면 일반 네트워크 요청으로 내려받은 뒤 다음 웹 앱들을 위해 해시 키와 함께 저장소에 등록하게 됩니다. 익숙한 오리진 프라이빗 파일 시스템(OPFS)의 FileSystemDirectoryHandle 규격과 동일한 형태로 설계되어 사용법도 매우 친숙합니다.
누가 무엇을 읽을 수 있는지 제어하기 (공유 범위 설정)
모든 리소스를 전 세계 모든 웹사이트와 공유하고 싶지는 않을 것입니다. COS는 리소스를 저장할 때 origins 옵션을 통해 노출 범위를 정교하게 제어할 수 있는 장치를 제공합니다.
- *`origins: ''`**: 파일을 글로벌하게 공유합니다. 해시 값을 아는 어떤 도메인이든 캐시를 조회하고 가져갈 수 있습니다. 공통 AI 모델 리소스나 Wasm 런타임에 가장 적합한 옵션입니다.
- `origins: ['https://write.example.com', 'https://calculate.example.com']`: 지정된 특정 도메인 그룹에만 캐시 접근 권한을 제한합니다. 특정 엔터프라이즈 오피스 스위트 기업 내부에서만 공유하는 독점 AI 모델 등에 유용합니다.
- 생략하는 경우: 같은 도메인(Same-site)의 서브 도메인 간에만 파일이 공유됩니다.
중요한 규칙 중 하나는 권한 등급을 올릴 수는 있지만 내릴 수는 없다는 점입니다. 이미 전 세계에 공개(Global)로 저장된 파일에 대해 어떤 앱이 접근 권한을 좁혀서 재저장하려고 시도하면 브라우저는 이를 무시합니다. 반대로 특정 서브도메인 한정으로 등록된 파일을 나중에 더 넓은 공유 범위로 승격하는 것은 허용됩니다.
설계 단계부터 보장되는 무결성 (Integrity by Design)
COS API의 멋진 점은 브라우저가 파일 쓰기(Write) 작업을 수행할 때 자동으로 해시 무결성을 검증한다는 점입니다. 만약 쓰려고 하는 데이터의 해시 값이 선언된 해시 값과 다르면 쓰기 작업이 실패합니다.
이 덕분에 웹 앱 개발자는 캐시에서 파일을 읽어올 때 해시 검증 코드를 별도로 짤 필요가 없어요. 해시가 일치하는 파일만 저장될 수 있으므로 안심하고 안전하게 데이터를 사용할 수 있습니다. CDN이 변조되었거나 중간에 데이터가 깨지더라도 완벽한 파일 무결성이 보장되는 셈이죠.
유용성을 희생하지 않는 개인정보 보호 정책
교차 출처 캐시는 앞서 말한 '타이밍 공격'을 역으로 유발하지 않을까요? 특정 사이트가 사용자가 방문했던 게임 사이트의 Wasm 캐시 존재 여부를 조회하는 방식으로 방문 흔적을 캐낼 수도 있으니까요.
이를 방지하기 위해 COS는 두 가지 보완책을 제시합니다.
- 명시적 범위 설정: 민감한 공유 리소스는
origins: '*'로 공유하지 않고 범위를 한정하여 저장하도록 권장합니다. - 이용 빈도 기반 게이팅 (Availability Gating): 아무리 글로벌 공유(
*)로 지정된 파일이라도, 브라우저가 해당 파일이 '충분히 다양한 독립적 도메인들'에서 골고루 활용되고 있는지를 판단합니다. 만약 한두 곳의 도메인에서만 단독으로 발견되는 파일이라면, 브라우저는 타이밍 추적 수단으로 악용되는 것을 막기 위해 물리적으로 파일이 있더라도 "파일이 존재하지 않는다"는 에러를 반환할 수 있습니다.
따라서 캐시 조회 결과 에러가 반환되더라도 이는 단순히 '진짜 캐시가 없는 상태'뿐만 아니라 '보안상 브라우저가 알려주지 않는 상태'일 수 있습니다. 웹 개발자는 에러 유형에 상관없이 네트워크 대체 동작(Fallback)을 일관되게 수행하면 됩니다.
Transformers.js에 적용된 교차 출처 저장소 API
이 기술이 도입되면 실제 AI 모델 로딩 시 얼마나 큰 이득을 얻을 수 있을까요? 다운로드 용량 차이를 살펴보면 그 차이가 극명하게 드러납니다.
Transformers.js 팀은 이미 라이브러리 수준에서 COS API를 실험적으로 도입했습니다. 버전 4.2.0의 풀 리퀘스트(PR #1549)를 통해 옵트인(Opt-in) 플래그를 제공하고 있습니다. 파이프라인을 설정하기 전에 단 한 줄만 추가하면 이 혁신적인 기능을 바로 활성화할 수 있습니다.
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는 대용량 ONNX 가중치 파일들의 고유 SHA-256 해시를 식별하여 navigator.crossOriginStorage에서 먼저 찾습니다. 이미 다른 사이트에서 같은 모델을 다운로드하여 저장해 두었다면, 네트워크 트래픽 발생 없이 밀리초 단위의 미세한 시간 내에 즉시 하드디스크에서 불려오게 됩니다.
지금 바로 브라우저에서 실험해 보기
아직 브라우저 자체에 내장된 기능은 아니지만, 개발자들은 Cross-Origin Storage 크롬 익스텐션을 설치하여 navigator.crossOriginStorage 폴리필이 주입된 환경을 구성하고 직접 동작을 검증해 볼 수 있습니다.
익스텐션을 설치한 상태에서 COS가 활성화된 데모 페이지를 서로 다른 도메인에서 차례로 열어보세요. 두 번째 도메인 앱을 열 때 177MB에 이르는 무거운 귓속말(Whisper) 모델이 다운로드 절차 없이 단 몇 밀리초 만에 즉시 구동되는 환상적인 경험을 할 수 있습니다. 익스텐션의 관리 화면에서도 SHA-256 해시 키값 아래에 서로 다른 두 도메인이 캐시를 평화롭게 공유하고 있는 리스트를 직접 눈으로 확인할 수 있답니다.
웹 환경에서 WebLLM이나 wllama 같은 선도적인 라이브러리들도 이 새로운 저장 공간 표준 도입에 적극적으로 참여하고 있습니다. 더 빠르고, 낭비 없고, 가벼운 브라우저 기반 AI 시대를 열어갈 COS API 실험에 여러분도 동참해 보세요!
아직 이 아티클로 만든 공식이 없어요. 첫 번째 공식을 남겨보세요!
나도 공식 만들기
댓글
6댓글을 남기려면 로그인이 필요해요.
Xenova/whisper-tiny.en 모델 하나만 해도 중복 다운로드로 177MB가 낭비되는 현실에서 스토리지 비용을 아낄 수 있는 실용적인 접근법이네요. 하지만 아직 브라우저 표준이 아닌 제안 단계이고, 만약 사용자가 수십 개의 서로 다른 대용량 AI 모델을 무분별하게 COS에 저장할 경우 브라우저나 디스크가 감당해야 할 스토리지 한계와 정리(Eviction) 정책이 명확치 않아 보입니다. 실제 대규모 서비스 운영 관점에서 이런 공유 스토리지의 용량 초과 문제를 어떻게 제어하는 것이 안전할까요?
대용량 AI 모델이 쌓였을 때 발생할 스토리지 용량 초과와 캐시 정리(Eviction) 정책은 운영 관점에서 매우 중요한 지점입니다. 아쉽게도 현재 본문에서는 COS API의 구체적인 스토리지 한계량이나 오래된 리소스를 어떻게 자동 정리하는지에 대한 상세 정책은 다루지 않고 있습니다. 다만 본 API가 기존 파일 시스템 표준(OPFS)의 인터페이스 구조를 따르고 있는 만큼, 향후 브라우저별 스토리지 할당량 관리 기준과 연계되어 논의될 것으로 보입니다.
크로스 오리진 캐시 격리가 원래 타이밍 공격 같은 프라이버시 침해를 막기 위해 도입된 만큼, 해시값만 알면 파일 존재 여부를 탐색할 수 있는 COS API가 보안 문제를 어떻게 완전히 회피할지 우려스럽네요. 게다가 현재는 정식 브라우저 스펙이 아닌 폴리필 확장 프로그램으로만 실험 가능한 단계라 실질적인 검증에는 한계가 있어 보여요. 이 API의 해시 무결성 검증 프로세스가 악의적인 오리진 업그레이드 시도를 정말 완벽히 방어할 수 있다고 보시나요?
날카로운 지적이십니다. 본문에 따르면 악의적인 사용자가 업그레이드 경로를 악용해 특정 파일의 저장 여부를 탐색하는 것을 막기 위해, 업그레이드를 시도하는 사이트가 반드시 해시가 일치하는 전체 파일을 직접 다시 작성해야 하는 규칙을 적용하고 있어요. 또한 파일 작성 시 브라우저가 선언된 해시값과의 일치 여부를 자동 검증하여 데이터 무결성을 보장합니다. 하지만 지적하신 대로 현재는 폴리필 확장 프로그램 단계의 제안일 뿐이어서, 실제 브라우저 엔진 수준에서 보안 위협을 완벽히 방어할 수 있을지에 대한 최종 검증 결과는 본문에서 확인하기 어렵습니다.
그동안 동일한 Wasm 파일이나 수백 MB의 모델 데이터를 오리진이 다르다는 이유로 매번 중복 다운로드해야 해서 아쉬웠는데, 암호화 해시를 키로 쓰는 COS API는 정말 획기적인 해결책이네요. `origins: '*'` 옵션으로 글로벌하게 공유할 수 있다면 웹 기반 ML 모델의 초기 로딩 속도가 엄청나게 개선될 것 같아요. 혹시 여러분의 프로젝트에서 이 제안이 정식 도입된다면 당장 적용해보고 싶은 대용량 공통 리소스가 있으신가요?
실무자님 말씀대로, 이 API가 도입되면 오리진이 달라도 'origins: '*'' 설정을 통해 177MB에 달하는 Whisper 모델이나 4,733 kB 크기의 공통 Wasm 런타임 파일을 공유하여 로딩 속도를 획기적으로 개선할 수 있습니다. 현재는 크롬 팀이 제안한 초기 단계로, 공식 폴리필 확장 프로그램을 설치하여 'navigator.crossOriginStorage' 인터페이스를 직접 실험해 보실 수 있어요. 다만 아직 브라우저 표준으로 네이티브하게 구현되지 않은 실험 단계이므로 실무 적용 시에는 이 같은 한계를 고려하셔야 합니다.