-
Notifications
You must be signed in to change notification settings - Fork 119
feat(serverless): network-volume warm cache (VolumeCache) #531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
deanq
wants to merge
17
commits into
main
Choose a base branch
from
deanquinanola/sls-367-network-volume-warm-cache-for-serverless-volumecache
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+977
−19
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
fec7fc3
feat(serverless): add VolumeCache skeleton with availability gating
deanq 2853f38
feat(serverless): VolumeCache.sync writes collision-free delta shards
deanq f0a76f8
fix(serverless): tolerate coarse network-fs mtime granularity in Volu…
deanq 4b0f6df
feat(serverless): VolumeCache.hydrate extracts shards with idempotent…
deanq b95e1f1
fix(serverless): drop 3.12-only tarfile filter kwarg to honor py3.10 …
deanq 6fc8250
fix(serverless): apply tar data filter when available, honoring py3.1…
deanq 5b76d8d
feat(serverless): harden VolumeCache extract against traversal and sy…
deanq b52bb48
fix(serverless): realpath-normalize cache dirs so symlinked mounts match
deanq fd19f65
feat(serverless): add size-capped retention to VolumeCache
deanq 68b56d3
feat(serverless): add best-effort guard and warm() context manager
deanq c3a9e98
feat(serverless): export VolumeCache from runpod.serverless
deanq a8f466a
feat(serverless): auto-hydrate model cache from network volume in wor…
deanq 1e8fdf1
fix(serverless): guard build_default_cache and isolate worker-wiring …
deanq f8fe045
fix(serverless): guard cache sync dispatch, sweep temp shards, cover …
deanq 53bb5a6
Merge branch 'main' into deanquinanola/sls-367-network-volume-warm-ca…
deanq ede2d5c
fix(serverless): address review - streaming sync, retention resilienc…
deanq eeb26e6
feat(serverless): make network-volume warm cache opt-in; add usage docs
deanq File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| # Network-Volume Warm Cache (VolumeCache) | ||
|
|
||
| `VolumeCache` warms local directories across serverless workers using a mounted | ||
| network volume. On cold start it restores previously-synced directories (for | ||
| example a model cache) from the volume; after use it syncs newly written files | ||
| back so the next cold worker starts warm. This turns a repeated multi-GB model | ||
| download on every cold start into a one-time cost per endpoint. | ||
|
|
||
| It is stdlib-only and best-effort: any failure degrades to a cold worker and | ||
| never raises into your handler or the worker loop. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - A **network volume** attached to the endpoint (mounted at `/runpod-volume`). | ||
| - Set on serverless automatically: `RUNPOD_ENDPOINT_ID` (used to scope the cache | ||
| per endpoint). | ||
|
|
||
| If no volume is mounted, every operation is a safe no-op. | ||
|
|
||
| ## Option 1: Built-in (opt-in, zero code) | ||
|
|
||
| The worker can hydrate a model cache at startup and sync it after the first job, | ||
| with no changes to your handler. It is **off by default** — enable it with an | ||
| environment variable: | ||
|
|
||
| ```bash | ||
| # Enable the built-in warm cache (opt-in) | ||
| RUNPOD_VOLUME_CACHE=1 | ||
| ``` | ||
|
|
||
| When enabled and a volume is mounted, the worker: | ||
|
|
||
| 1. Hydrates the model cache from the volume before the first job runs. | ||
| 2. Syncs new files to the volume once, after the first successful job. | ||
|
|
||
| By default it caches the directories pointed to by `HF_HOME`, `HF_HUB_CACHE`, | ||
| and `TORCH_HOME` (whichever are set; `HF_HOME` defaults to | ||
| `~/.cache/huggingface`). Add more directories with `RUNPOD_CACHE_DIRS`. | ||
|
|
||
| ### Configuration | ||
|
|
||
| | Variable | Default | Purpose | | ||
| | --- | --- | --- | | ||
| | `RUNPOD_VOLUME_CACHE` | unset (off) | Set to `1`/`true`/`yes`/`on` to enable the built-in. | | ||
| | `RUNPOD_CACHE_DIRS` | — | Extra directories to cache, `os.pathsep`-separated (`:` on Linux). | | ||
| | `RUNPOD_VOLUME_CACHE_MAX_GB` | `50` | Prune oldest shards once the endpoint's cache exceeds this size. | | ||
| | `HF_HOME` / `HF_HUB_CACHE` / `TORCH_HOME` | — | Auto-discovered model-cache locations. | | ||
|
|
||
| ### Example | ||
|
|
||
| For most model-serving workers, enabling the built-in and letting the model | ||
| download into `HF_HOME` on the first request is all that is needed: | ||
|
|
||
| ```python | ||
| # my_worker.py | ||
| import runpod | ||
|
|
||
| def handler(job): | ||
| # First cold worker downloads the model into HF_HOME; the built-in syncs it | ||
| # to the volume. Subsequent cold workers hydrate it and skip the download. | ||
| ... | ||
|
|
||
| runpod.serverless.start({"handler": handler}) | ||
| ``` | ||
|
|
||
| ```bash | ||
| # Template / container env | ||
| RUNPOD_VOLUME_CACHE=1 | ||
| ``` | ||
|
|
||
| > Note: hydration only helps model loads that happen **after** the worker starts | ||
| > (lazy or in-handler loading). If your model loads at module import time, use | ||
| > Option 2 to place `warm()` around the load. | ||
|
|
||
| ## Option 2: Explicit API | ||
|
|
||
| Import `VolumeCache` and control caching yourself. This works for any | ||
| directories, and the `warm()` context manager guarantees hydration happens | ||
| before your model load regardless of when it runs. | ||
|
|
||
| ```python | ||
| from runpod.serverless import VolumeCache | ||
|
|
||
| vc = VolumeCache(dirs=["/root/.cache/huggingface"]) | ||
|
|
||
| with vc.warm(): # hydrate on enter, sync the delta on exit | ||
| model = load_model() # downloads land in the cached directory | ||
| ``` | ||
|
|
||
| You can also call the phases directly when they happen at different points in | ||
| your worker's lifecycle: | ||
|
|
||
| ```python | ||
| vc = VolumeCache(dirs=["/data/models"], namespace="my-model-cache", max_size_gb=100) | ||
|
|
||
| vc.hydrate() # restore cached files (e.g. at startup) | ||
| model = load_model() # populate the cache | ||
| vc.sync() # persist new files back to the volume | ||
| ``` | ||
|
|
||
| ### Constructor | ||
|
|
||
| | Argument | Default | Purpose | | ||
| | --- | --- | --- | | ||
| | `dirs` | required | Local directories to cache. | | ||
| | `namespace` | `RUNPOD_ENDPOINT_ID` | Isolation key for the on-volume shards. Must be a single safe path component. | | ||
| | `volume_path` | `/runpod-volume` | Network-volume mount point. | | ||
| | `max_size_gb` | `None` | Prune oldest shards past this cap; `None` = no cap. | | ||
| | `best_effort` | `True` | Swallow and log errors instead of raising. Set `False` while debugging. | | ||
|
|
||
| ## How it works | ||
|
|
||
| - **Per-endpoint, sharded storage.** Each worker writes its delta as its own tar | ||
| shard under `/<volume>/.cache/<namespace>/`, published atomically. Per-worker | ||
| shards are collision-free under concurrent workers — no shared file is | ||
| rewritten. Hydration extracts all shards for the namespace (newest wins on | ||
| overlap). | ||
| - **Delta only.** `sync()` packs just the files written since the last hydrate, | ||
| not the whole cache. | ||
| - **Retention.** With `max_size_gb` set, the oldest shards are pruned once the | ||
| namespace exceeds the cap. | ||
| - **Safety.** Extraction rejects members that would escape the configured | ||
| directories (traversal, symlinks, absolute paths). | ||
|
|
||
| ## Limitations | ||
|
|
||
| - **Cold-scale write amplification.** If N workers cold-start at the same time, | ||
| each misses the still-empty cache, downloads the model, and writes a full-size | ||
| shard. Total volume writes on the first scale-up are ~N×; bounded thereafter by | ||
| retention. | ||
| - **Lost first warm on aggressive recycle.** `sync()` runs in a background thread | ||
| after the job response. A large-model sync may not finish before the worker is | ||
| recycled; the partial shard is cleaned up and the warm is retried on the next | ||
| worker. | ||
|
|
||
| ## Status | ||
|
|
||
| The built-in is opt-in for now (`RUNPOD_VOLUME_CACHE=1`). Whether it should | ||
| default on when a volume is present is still under review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.