Skip to content

[Web][COS] Persist URL→hash mapping across page loads#19569

Merged
akaashrp merged 3 commits into
apache:mainfrom
tomayac:cos-refinement
Jun 1, 2026
Merged

[Web][COS] Persist URL→hash mapping across page loads#19569
akaashrp merged 3 commits into
apache:mainfrom
tomayac:cos-refinement

Conversation

@tomayac

@tomayac tomayac commented May 15, 2026

Copy link
Copy Markdown
Contributor

The CrossOriginStorage class was storing the URL→hash map only in the module-level GLOBAL_HASH_CACHE. After a page reload that cache is empty, and getFileHash() can only recover hashes for HuggingFace LFS files (URLs containing /resolve/). This left several resource categories uncacheable across sessions:

Screenshot 2026-05-15 at 17 43 15
  • JSON files not stored in LFS (mlc-chat-config.json, tokenizer.json, tensor-cache.json) — getFileHash returns null for their /resolve/ URLs because the raw pointer is the actual file content, not an LFS pointer.
  • .wasm files from GitHub raw URLs — no /resolve/ pattern at all.
  • Any file whose hash was computed from blob content via getBlobHash.

Additionally, even for genuine LFS model shards, each page load was re-fetching every shard's LFS pointer file over the network just to re-derive the SHA-256 hash.

Fix: persist the URL→hash mapping to a dedicated Cache API store (tvmjs-cos-hash-meta). Two write sites:

  1. put() — after a file is stored in COS, persist its blob-derived hash. This covers all non-LFS files and non-HuggingFace URLs.

  2. resolveHashDescriptor() — after getFileHash() resolves a hash from the LFS pointer, persist it immediately. This eliminates repeated pointer-file network requests for model shards on subsequent visits.

Both write sites use a best-effort try/catch so storage quota errors are silently ignored. loadPersistedHashEntry() similarly swallows errors. The typeof caches === "undefined" guard keeps the code safe in Node.js test environments.

The CrossOriginStorage class was storing the URL→hash map only in the
module-level GLOBAL_HASH_CACHE. After a page reload that cache is empty,
and getFileHash() can only recover hashes for HuggingFace LFS files
(URLs containing /resolve/). This left several resource categories
uncacheable across sessions:

- JSON files not stored in LFS (mlc-chat-config.json, tokenizer.json,
  tensor-cache.json) — getFileHash returns null for their /resolve/ URLs
  because the raw pointer is the actual file content, not an LFS pointer.
- .wasm files from GitHub raw URLs — no /resolve/ pattern at all.
- Any file whose hash was computed from blob content via getBlobHash.

Additionally, even for genuine LFS model shards, each page load was
re-fetching every shard's LFS pointer file over the network just to
re-derive the SHA-256 hash.

Fix: persist the URL→hash mapping to a dedicated Cache API store
(tvmjs-cos-hash-meta). Two write sites:

1. put() — after a file is stored in COS, persist its blob-derived hash.
   This covers all non-LFS files and non-HuggingFace URLs.

2. resolveHashDescriptor() — after getFileHash() resolves a hash from
   the LFS pointer, persist it immediately. This eliminates repeated
   pointer-file network requests for model shards on subsequent visits.

Both write sites use a best-effort try/catch so storage quota errors are
silently ignored. loadPersistedHashEntry() similarly swallows errors.
The typeof caches === "undefined" guard keeps the code safe in Node.js
test environments.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements persistent caching for hash descriptors in CrossOriginStorage using the browser's Cache API. It introduces persistHashEntry and loadPersistedHashEntry methods to store and retrieve hashes, reducing redundant network requests for non-LFS files and specific URLs on subsequent visits. I have no feedback to provide as there were no review comments.

@tomayac

tomayac commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Most likely @CharlieFRuan, @guan404ming, and @akaashrp would be good reviewers for this refinement.

@akaashrp

Copy link
Copy Markdown
Contributor

Thanks for the ping @tomayac. Will take a look soon.

@akaashrp

Copy link
Copy Markdown
Contributor

@tomayac Apologies for the delay. This looks good to me. Could you rebase so that CI checks pass?

@tomayac

tomayac commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@tomayac Apologies for the delay. This looks good to me. Could you rebase so that CI checks pass?

Thanks so much! Can you try again now?

@guan404ming guan404ming left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@akaashrp

Copy link
Copy Markdown
Contributor

@tomayac you might need to commit again to retrigger CI, not sure what's up with it

Comment thread web/src/artifact_cache.ts Outdated
@tomayac

tomayac commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@tomayac you might need to commit again to retrigger CI, not sure what's up with it

Made a commit, and CI seems to be running now.

@tomayac

tomayac commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

⏳ Oh, please wait before merging. I want to test one more thing before.

@tomayac

tomayac commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

⏳ Oh, please wait before merging. I want to test one more thing before.

OK, we should be good. All resources that we now additionally cache here use some sort of versioning string in the path of their URLs (e.g, https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_83/base/Llama-3.2-3B-Instruct-q4f32_1_cs1k-webgpu.wasm), so we can indeed use the URL as the cache key (await this.persistHashEntry(url, hash)) and not be stuck with an outdated version should the resource change. Ready to merge.

@akaashrp akaashrp merged commit 8039963 into apache:main Jun 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants