blob: da655da4e317d90fe8a64c8273946c321a5194a1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { OPFSStore, type OPFSAccessMode } from "./opfs_store";
export type { OPFSAccessMode } from "./opfs_store";
export interface TensorCacheEntry {
name: string;
shape: Array<number>;
dtype: string;
format: "f32-to-bf16" | "raw";
byteOffset: number;
nbytes: number;
}
export interface TensorShardEntry {
dataPath: string;
format: "raw-shard";
nbytes: number;
records: Array<TensorCacheEntry>;
}
/**
* Common Interface for the artifact cache
*/
export interface ArtifactCacheTemplate {
/**
* Retrieve data object that corresponds to `url` from cache. If data object does not exist in
* cache, fetch the data and then add to cache.
*
* @param url The url to the data to be cached.
* @param storetype This field is required so that `ArtifactIndexedDBCache` can store the
* actual data object (see `addToCache()`), while `ArtifactCache` which uses the Cache API can
* return the actual data object rather than the request. There are two options:
* 1. "json": returns equivalent to `fetch(url).json()`
* 2. "arraybuffer": returns equivalent to `fetch(url).arraybuffer()`
* @param signal An optional AbortSignal allowing user to abort the fetching before its completion.
* @return The data object (i.e. users do not need to call `.json()` or `.arraybuffer()`).
*
* Note: This is an async function.
*/
fetchWithCache(url: string, storetype?: string, signal?: AbortSignal): Promise<any>;
/**
* Fetch data from url and add into cache. If already exists in cache, should return instantly.
*
* @param url The url to the data to be cached.
* @param storetype Only applies to `ArtifactIndexedDBCache`. Since `indexedDB` stores the actual
* @param signal An optional AbortSignal to abort data retrival.
* data rather than a request, we specify `storagetype`. There are two options:
* 1. "json": IndexedDB stores `fetch(url).json()`
* 2. "arraybuffer": IndexedDB stores `fetch(url).arrayBuffer()`
*
* Note: This is an async function.
*/
addToCache(url: string, storetype?: string, signal?: AbortSignal): Promise<void>;
/**
* check if cache has all keys in Cache
*
* Note: This is an async function.
*/
hasAllKeys(keys: string[]): Promise<boolean>;
/**
* Delete url in cache if url exists
*
* Note: This is an async function.
*/
deleteInCache(url: string): Promise<void>;
}
export type ArtifactCacheType = "cache" | "indexeddb" | "cross-origin" | "opfs";
export interface TensorCacheAccessOptions {
cacheScope?: string;
cacheType?: ArtifactCacheType;
artifactCache?: ArtifactCacheTemplate;
opfsAccessMode?: OPFSAccessMode;
}
type StoreType = string | undefined;
type RequestLike = string | URL | Request | { url?: string };
interface CrossOriginHashDescriptor {
algorithm: string;
value: string;
}
interface CrossOriginStorageHandle {
getFile(): Promise<Blob>;
createWritable(): Promise<CrossOriginStorageWritable>;
}
interface CrossOriginStorageRequestFileHandleOptions {
create?: boolean;
origins?: string[] | string | undefined;
}
interface CrossOriginStorageWritable {
write(data: Blob): Promise<void>;
close(): Promise<void>;
}
interface CrossOriginStorageAPI {
requestFileHandle(
descriptor: CrossOriginHashDescriptor,
options?: CrossOriginStorageRequestFileHandleOptions,
): Promise<CrossOriginStorageHandle>;
}
declare global {
interface Navigator {
crossOriginStorage?: CrossOriginStorageAPI;
}
interface WorkerNavigator {
crossOriginStorage?: CrossOriginStorageAPI;
}
}
const HASH_ALGORITHM = "SHA-256";
const DEFAULT_FETCH_OPTIONS: RequestInit = { method: "GET" };
const COS_HASH_META_CACHE = "tvmjs-cos-hash-meta";
let crossOriginFallbackWarningLogged = false;
const GLOBAL_HASH_CACHE = new Map<
string,
CrossOriginHashDescriptor
>();
class CrossOriginStorage {
private hashCache: Map<string, CrossOriginHashDescriptor>;
constructor() {
this.hashCache = GLOBAL_HASH_CACHE;
}
static isAvailable(): boolean {
if (typeof navigator === "undefined") {
return false;
}
return navigator.crossOriginStorage !== undefined;
}
async match(request: RequestLike): Promise<Response | undefined> {
const url = this.normalizeRequest(request);
const hash = await this.resolveHashDescriptor(url);
if (!hash) {
return undefined;
}
try {
const api = this.getApi();
if (!api) {
return undefined;
}
const handle = await api.requestFileHandle(hash);
if (!handle) {
return undefined;
}
const blob = await handle.getFile();
return new Response(blob);
} catch {
return undefined;
}
}
async put(request: RequestLike, response: Response): Promise<void> {
const url = this.normalizeRequest(request);
const blob = await response.blob();
const hash = await this.getBlobHash(blob);
const api = this.getApi();
if (!api) {
throw new Error("Cross-origin storage API unavailable.");
}
const handle = await api.requestFileHandle(hash, { create: true, origins: "*" /* All origins */ });
if (!handle) {
throw new Error("Cross-origin storage API returned no handle.");
}
const writableStream = await handle.createWritable();
await writableStream.write(blob);
await writableStream.close();
this.hashCache.set(url, hash);
await this.persistHashEntry(url, hash);
}
async delete(_request: RequestLike): Promise<void> {
// Cross-origin storage extension currently has no delete API.
return;
}
private getApi(): CrossOriginStorageAPI | undefined {
if (!CrossOriginStorage.isAvailable()) {
return undefined;
}
return navigator.crossOriginStorage;
}
private normalizeRequest(request: RequestLike): string {
if (typeof request === "string") {
return request;
}
if (request instanceof URL) {
return request.href;
}
if (request instanceof Request) {
return request.url;
}
if (request && typeof request.url === "string") {
return request.url;
}
throw new Error("CrossOriginStorage: Unsupported request type.");
}
private async persistHashEntry(
url: string,
hash: CrossOriginHashDescriptor,
): Promise<void> {
try {
if (typeof caches === "undefined") {
return;
}
const store = await caches.open(COS_HASH_META_CACHE);
await store.put(url, new Response(JSON.stringify(hash)));
} catch {
// best-effort: ignore storage errors
}
}
private async loadPersistedHashEntry(
url: string,
): Promise<CrossOriginHashDescriptor | null> {
try {
if (typeof caches === "undefined") {
return null;
}
const store = await caches.open(COS_HASH_META_CACHE);
const response = await store.match(url);
if (!response) {
return null;
}
return JSON.parse(await response.text()) as CrossOriginHashDescriptor;
} catch {
return null;
}
}
private async resolveHashDescriptor(
url: string,
): Promise<CrossOriginHashDescriptor | null> {
const cached = this.hashCache.get(url);
if (cached) {
return cached;
}
// Check persistent store before falling back to network-based hash extraction.
// This covers non-LFS files (JSON configs, tokenizers) and non-HuggingFace URLs
// (e.g. GitHub raw .wasm files) whose hashes were computed from blob content on a
// previous visit and persisted to the Cache API.
const persisted = await this.loadPersistedHashEntry(url);
if (persisted) {
this.hashCache.set(url, persisted);
return persisted;
}
const hashValue = await this.getFileHash(url);
if (!hashValue) {
return null;
}
const descriptor: CrossOriginHashDescriptor = {
algorithm: HASH_ALGORITHM,
value: hashValue,
};
this.hashCache.set(url, descriptor);
// Persist pointer-derived hashes so subsequent visits skip the LFS pointer
// network request (especially important for models with many shards).
await this.persistHashEntry(url, descriptor);
return descriptor;
}
private async getFileHash(url: string): Promise<string | null> {
if (/\/resolve\//.test(url)) {
const pointerHash = await this.extractHashFromPointer(url);
if (pointerHash) {
return pointerHash;
}
}
return null;
}
private async extractHashFromPointer(url: string): Promise<string | null> {
const rawUrl = url.replace(/\/resolve\//, "/raw/");
try {
const text = await fetch(rawUrl).then((res) => res.text());
if (!text.includes("oid sha256:")) {
return null;
}
const match = text.match(/oid sha256:([A-Fa-f0-9]+)/);
return match ? match[1] : null;
} catch {
return null;
}
}
private async getBlobHash(blob: Blob): Promise<CrossOriginHashDescriptor> {
const arrayBuffer = await blob.arrayBuffer();
const hashBuffer = await crypto.subtle.digest(HASH_ALGORITHM, arrayBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
return {
algorithm: HASH_ALGORITHM,
value: hashHex,
};
}
}
/**
* Cache to store model related data, implemented with the Cache API.
*/
export class ArtifactCache implements ArtifactCacheTemplate {
private scope: string;
private cache?: Cache;
constructor(scope: string) {
this.scope = scope;
}
/**
* Convert the Response object to the expected storetype instead
*/
async responseTostoretype(response: Response, storetype?: string): Promise<any> {
if (storetype === undefined) {
return response;
} else if (storetype.toLowerCase() === "json") {
return await response.json();
} else if (storetype.toLowerCase() === "arraybuffer") {
return await response.arrayBuffer();
} else {
console.error("Unknown storage type " + storetype + ", returning raw response");
return response;
}
}
/**
* fetch the corresponding url object in response or stored object format
* @param url url
* @param storetype the storage type for indexedDB
* @param signal an optional abort signal to abort fetching
* @returns response in json, arraybuffer or pure response format
*/
async fetchWithCache(url: string, storetype?: string, signal?: AbortSignal): Promise<any> {
await this.addToCache(url, storetype, signal);
const result = await this.cache.match(new Request(url));
if (result === undefined) {
// Already called `addToCache()`, should expect the request in cache.
throw Error("Cannot fetch " + url);
}
return await this.responseTostoretype(result, storetype);
}
async addToCache(url: string, storetype?: string, signal?: AbortSignal) {
const request = new Request(url, signal ? { signal } : undefined);
if (this.cache === undefined) {
this.cache = await caches.open(this.scope);
}
const result = await this.cache.match(request);
if (result === undefined) {
await this.cache.add(request);
}
}
/**
* Determine if all keys exist in the cache
* @param keys the url key list of the strings
* @returns boolean value indicate if all keys are in cache
*/
async hasAllKeys(keys: string[]) {
if (this.cache === undefined) {
this.cache = await caches.open(this.scope);
}
return this.cache.keys()
.then(requests => requests.map(request => request.url))
.then(cacheKeys => keys.every(key => cacheKeys.indexOf(key) !== -1))
.catch(() => false);
}
/**
* Delete the corresponding url object in cache
* @param url the corresponding url object to be deleted
*/
async deleteInCache(url: string) {
if (this.cache === undefined) {
this.cache = await caches.open(this.scope);
}
await this.cache.delete(url);
}
}
/**
* Cache by IndexedDB to support caching model data
*/
export class ArtifactIndexedDBCache implements ArtifactCacheTemplate {
private dbName?: string;
private dbVersion = 1;
private db: IDBDatabase | undefined;
constructor(dbName: string) {
this.dbName = dbName;
}
/**
* Init the indexed DB database if it is not initialized.
*/
private async initDB() {
if (this.db != null) {
return; // the db is already inialized
}
return new Promise<void>((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onupgradeneeded = (event) => {
this.db = (event.target as IDBOpenDBRequest).result;
if (!this.db.objectStoreNames.contains('urls')) {
this.db.createObjectStore('urls', { keyPath: 'url' });
}
};
request.onsuccess = (event) => {
this.db = (event.target as IDBOpenDBRequest).result;
resolve();
};
request.onerror = (event) => {
console.error("Database error: ", (event.target as IDBOpenDBRequest).error);
reject((event.target as IDBOpenDBRequest).error);
};
});
}
/**
* Check if current url object is in indexedDB or not
* @param url the url link
* @returns boolean indicate if url object in indexedDB
*/
private async isUrlInDB(url: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const transaction = this.db?.transaction(['urls'], 'readonly');
if (transaction === undefined) {
return false;
}
const store = transaction.objectStore('urls');
const request = store.get(url);
request.onsuccess = () => {
resolve(request.result !== undefined);
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
}
async asyncGetHelper(url: string): Promise<any> {
return new Promise((resolve, reject) => {
let result: any;
const transaction = this.db?.transaction(['urls'], 'readonly');
if (transaction === undefined) {
return false;
}
transaction.oncomplete = () => resolve(result);
transaction.onerror = () => reject(transaction.error);
const objectStore = transaction.objectStore('urls');
const getRequest = objectStore.get(url);
getRequest.onsuccess = () => {
result = getRequest.result;
}
})
}
async fetchWithCache(url: string, storetype?: string, signal?: AbortSignal): Promise<any> {
await this.addToCache(url, storetype, signal);
let result = await this.asyncGetHelper(url);
if (result === null) {
// previously null data in cache or somehow failed to add to cache, delete and retry
await this.deleteInCache(url);
await this.addToCache(url, storetype);
result = await this.asyncGetHelper(url);
}
if (result != null && typeof result === "object" && "data" in result) {
// `storetype` not used here because the data stored in indexedDB is already in that type
return result.data;
}
throw Error("ArtifactIndexedDBCache failed to fetch: " + url);
}
async addToIndexedDB(url: string, response: any, storetype?: string) {
await this.initDB();
let data: any;
// IndexedDB, unlike the Cache API, stores the actual data object, so we convert reponse here.
if (storetype != undefined) {
if (storetype.toLowerCase() === "json") {
data = await response.json();
} else if (storetype.toLocaleLowerCase() === "arraybuffer") {
data = await response.arrayBuffer();
} else {
throw Error("Unsupported storetyp for IndexedDB: " + storetype);
}
}
return new Promise<void>((resolve, reject) => {
const transaction = this.db?.transaction(['urls'], 'readwrite');
if (transaction === undefined) {
return;
}
const store = transaction.objectStore('urls');
const request = store.add({ data, url }); // Index DB follows a {value, key} format, instead of {key, value} format!
request.onsuccess = () => resolve();
request.onerror = (event) => reject((event.target as IDBRequest).error);
});
}
async addToCache(url: string, storetype?: string, signal?: AbortSignal): Promise<void> {
await this.initDB(); // await the initDB process
// If already cached, nothing to do
const isInDB = await this.isUrlInDB(url);
if (isInDB) {
return;
}
try {
const response = await fetch(url, signal ? { signal } : undefined);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const response_copy = response.clone();
await this.addToIndexedDB(url, response_copy, storetype);
} catch (error) {
throw Error("Failed to store " + url + " with error: " + error);
}
}
async hasAllKeys(keys: string[]): Promise<boolean> {
await this.initDB(); // Ensure the DB is initialized
if (!this.db) {
throw new Error('Database is not initialized');
}
return new Promise<boolean>((resolve, reject) => {
const transaction = this.db.transaction(['urls'], 'readonly');
const store = transaction.objectStore('urls');
const promises = keys.map(key => {
return new Promise<boolean>((resolve) => {
const request = store.get(key);
request.onsuccess = () => {
if (request.result === undefined) {
resolve(false); // Key not found, resolve with false
} else {
resolve(true); // Key found, resolve with true
}
};
request.onerror = () => {
resolve(false); // On error, resolve as if the key was not found
};
});
});
Promise.all(promises).then(results => {
const allExist = results.every(exists => exists);
resolve(allExist);
}).catch(error => {
reject(error); // Reject the main promise if any of the promises are rejected
});
});
}
async deleteInCache(url: string) {
await this.initDB(); // Make sure the DB is initialized
const transaction = this.db?.transaction(['urls'], 'readwrite');
if (transaction === undefined) {
return;
}
const store = transaction.objectStore('urls');
const request = store.delete(url);
// Await completion of the delete request
await new Promise<void>((resolve, reject) => {
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
return;
}
}
/**
* Cache by Origin Private File System (OPFS).
*/
export class ArtifactOPFSCache implements ArtifactCacheTemplate {
private readonly store: OPFSStore;
constructor(scope: string, accessMode: OPFSAccessMode = "async") {
this.store = new OPFSStore(scope, accessMode);
}
static isAvailable(): boolean {
return OPFSStore.isAvailable();
}
async fetchWithCache(
url: string,
storetype?: string,
signal?: AbortSignal,
): Promise<any> {
// Try the cache first to avoid a redundant OPFS lookup on a hit.
const cached = await this.readFromCache(url, storetype);
if (cached !== undefined) {
return cached;
}
await this.addToCache(url, storetype, signal);
const fetched = await this.readFromCache(url, storetype);
if (fetched === undefined) {
throw new Error("ArtifactOPFSCache failed to fetch: " + url);
}
return fetched;
}
private async readFromCache(
url: string,
storetype?: string,
): Promise<any> {
if (storetype?.toLowerCase() === "arraybuffer") {
return this.store.readArrayBuffer(url);
}
const cachedResponse = await this.store.read(url);
if (cachedResponse === undefined) {
return undefined;
}
return this.responseToStoreType(cachedResponse, storetype);
}
async addToCache(
url: string,
_storetype?: string,
signal?: AbortSignal,
): Promise<void> {
if (await this.store.has(url)) {
return;
}
const request = new Request(
url,
signal ? { ...DEFAULT_FETCH_OPTIONS, signal } : DEFAULT_FETCH_OPTIONS,
);
const response = await fetch(request);
if (!response.ok) {
throw new Error(
`ArtifactOPFSCache: Unable to fetch ${url}, received status ${response.status}`,
);
}
await this.store.write(url, response);
}
async hasAllKeys(keys: string[]): Promise<boolean> {
const results = await Promise.all(
keys.map(async (key) => await this.store.has(key)),
);
return results.every((result) => result);
}
async deleteInCache(url: string): Promise<void> {
await this.store.remove(url);
}
private async responseToStoreType(
response: Response,
storetype?: StoreType,
): Promise<any> {
if (storetype === undefined) {
return response;
}
const format = storetype.toLowerCase();
if (format === "json") {
return response.json();
}
if (format === "arraybuffer") {
return response.arrayBuffer();
}
return response;
}
}
/**
* Cache by cross-origin storage extension.
*/
export class ArtifactCrossOriginStorageCache implements ArtifactCacheTemplate {
private storage: CrossOriginStorage;
constructor(
_scope: string,
storage: CrossOriginStorage = new CrossOriginStorage(),
) {
this.storage = storage;
}
async fetchWithCache(
url: string,
storetype?: StoreType,
signal?: AbortSignal,
): Promise<any> {
const cachedResponse = await this.storage.match(url);
if (cachedResponse !== undefined) {
return this.responseToStoreType(cachedResponse, storetype);
}
await this.addToCache(url, storetype, signal);
const hydrated = await this.storage.match(url);
if (hydrated === undefined) {
throw new Error(`ArtifactCrossOriginStorageCache: failed to hydrate ${url}`);
}
return this.responseToStoreType(hydrated, storetype);
}
async addToCache(
url: string,
_storetype?: StoreType,
signal?: AbortSignal,
): Promise<void> {
const existing = await this.storage.match(url);
if (existing !== undefined) {
return;
}
const request = new Request(
url,
signal ? { ...DEFAULT_FETCH_OPTIONS, signal } : DEFAULT_FETCH_OPTIONS,
);
const response = await fetch(request);
if (!response.ok) {
throw new Error(
`ArtifactCrossOriginStorageCache: Unable to fetch ${url}, received status ${response.status}`,
);
}
await this.storage.put(url, response.clone());
}
async hasAllKeys(keys: string[]): Promise<boolean> {
const results = await Promise.all(
keys.map(async (key) => {
const cached = await this.storage.match(key);
return cached !== undefined;
}),
);
return results.every((result) => result);
}
async deleteInCache(url: string): Promise<void> {
await this.storage.delete(url);
}
private async responseToStoreType(
response: Response,
storetype?: StoreType,
): Promise<any> {
if (storetype === undefined) {
return response;
}
const format = storetype.toLowerCase();
if (format === "json") {
return response.json();
}
if (format === "arraybuffer") {
return response.arrayBuffer();
}
return response;
}
}
function normalizeCacheType(cacheType?: string): ArtifactCacheType {
if (cacheType === undefined) {
return "cache";
}
const normalized = cacheType.toLowerCase();
if (normalized === "cache") {
return "cache";
}
if (normalized === "indexeddb") {
return "indexeddb";
}
if (normalized === "cross-origin") {
return "cross-origin";
}
if (normalized === "opfs") {
return "opfs";
}
console.error("Unsupported cacheType: " + cacheType + ", using default ArtifactCache.");
return "cache";
}
function isTensorCacheAccessOptions(
value: string | TensorCacheAccessOptions | undefined,
): value is TensorCacheAccessOptions {
return typeof value === "object" && value !== null;
}
function normalizeCacheAccessOptions(
cacheScopeOrOptions: string | TensorCacheAccessOptions | undefined,
cacheType?: string,
): TensorCacheAccessOptions {
if (isTensorCacheAccessOptions(cacheScopeOrOptions)) {
return cacheScopeOrOptions;
}
return {
cacheScope: cacheScopeOrOptions,
cacheType: normalizeCacheType(cacheType),
};
}
export function createArtifactCache(
scope: string,
options: TensorCacheAccessOptions = {},
): ArtifactCacheTemplate {
if (options.artifactCache !== undefined) {
return options.artifactCache;
}
const cacheType = normalizeCacheType(options.cacheType);
if (cacheType === "indexeddb") {
return new ArtifactIndexedDBCache(scope);
}
if (cacheType === "cross-origin") {
if (CrossOriginStorage.isAvailable()) {
return new ArtifactCrossOriginStorageCache(scope);
}
if (!crossOriginFallbackWarningLogged) {
console.warn(
"Cross-origin storage backend is unavailable; falling back to ArtifactCache.",
);
crossOriginFallbackWarningLogged = true;
}
}
if (cacheType === "opfs") {
return new ArtifactOPFSCache(scope, options.opfsAccessMode);
}
return new ArtifactCache(scope);
}
/**
* Function to check if NDarray is in Cache or not
*
* @param tensorCacheUrl The cache url which links to the Tensor
* @param cacheScope The scope identifier of the cache
* @param cacheType The type of the cache: "cache", "indexedDB", "cross-origin", or "opfs"
* @returns the result if the cache has Tensor
*/
export async function hasTensorInCache(
tensorCacheUrl: string,
options?: TensorCacheAccessOptions,
): Promise<boolean>;
export async function hasTensorInCache(
tensorCacheUrl: string,
cacheScope?: string,
cacheType?: string,
): Promise<boolean>;
export async function hasTensorInCache(
tensorCacheUrl: string,
cacheScopeOrOptions: string | TensorCacheAccessOptions = "tvmjs",
cacheType = "cache",
): Promise<boolean> {
const options = normalizeCacheAccessOptions(cacheScopeOrOptions, cacheType);
const cacheScope = options.cacheScope ?? "tvmjs";
const artifactCache = createArtifactCache(cacheScope, options);
const jsonUrl = new URL("tensor-cache.json", tensorCacheUrl).href;
const hasJsonUrlInCache = await artifactCache.hasAllKeys([jsonUrl]);
if (!hasJsonUrlInCache) {
return false;
}
const list = (await artifactCache.fetchWithCache(
jsonUrl,
"json",
))["records"] as Array<TensorShardEntry>;
return await artifactCache.hasAllKeys(list.map(key => new URL(key.dataPath, tensorCacheUrl).href));
}
/**
* Given cacheUrl, search up items to delete based on cacheUrl/tensor-cache.json
*
* @param cacheUrl The cacheUrl for the items
* @param cacheScope The scope identifier of the cache
* @param cacheType The type of the cache: "cache", "indexedDB", "cross-origin", or "opfs"
*/
export async function deleteTensorCache(
cacheUrl: string,
options?: TensorCacheAccessOptions,
): Promise<void>;
export async function deleteTensorCache(
cacheUrl: string,
cacheScope?: string,
cacheType?: string,
): Promise<void>;
export async function deleteTensorCache(
cacheUrl: string,
cacheScopeOrOptions: string | TensorCacheAccessOptions = "tvmjs",
cacheType = "cache",
): Promise<void> {
const options = normalizeCacheAccessOptions(cacheScopeOrOptions, cacheType);
const cacheScope = options.cacheScope ?? "tvmjs";
const artifactCache = createArtifactCache(cacheScope, options);
if (artifactCache instanceof ArtifactCrossOriginStorageCache) {
// Cross-origin storage extension does not currently support programmatic deletion.
return;
}
const jsonUrl = new URL("tensor-cache.json", cacheUrl).href;
const list = await artifactCache.fetchWithCache(jsonUrl, "json");
const arrayentry = list["records"] as Array<TensorShardEntry>;
const processShard = async (i: number) => {
const dataUrl = new URL(arrayentry[i].dataPath, cacheUrl).href;
await artifactCache.deleteInCache(dataUrl);
}
await Promise.all(arrayentry.map((_, index) => processShard(index)));
}