vesuvius.predict: --chunk_cache_mb exposes the in-memory chunk cache (#1325) - #1808
Draft
CVasilopoulos wants to merge 1 commit into
Draft
CVasilopoulos wants to merge 1 commit into
CVasilopoulos wants to merge 1 commit into
Conversation
ScrollPrize#1373 added an opt-in LRU chunk cache to open_zarr and Volume (cache=True, cache_size_mb), built on zarr's CacheStore, but vesuvius.predict had no way to turn it on. Overlapping patches therefore fetch the same remote chunks again and again (ScrollPrize#1325). --chunk_cache_mb N passes cache=True, cache_size_mb=N from Inferer through VCDataset to Volume. The default is 0, which leaves predict unchanged. Each DataLoader worker holds its own cache of up to N MB, and negative values are rejected. Counted on published Scroll 1 data by wrapping fsspec HTTPFileSystem._cat_file in every process, including DataLoader workers: 54keV_7.91um_Scroll1A.zarr from dl.ash2txt.org, --bbox 6400:6592,5120:5312,5120:5312, overlap 0.5, batch 1, CPU. Upstream main 757f70c compared with this change: - patch 192^3, 64 patches, 125 distinct chunks: main 1000 chunk GETs (796 MB). This change with the default is the same. With --chunk_cache_mb 512 and 4 workers: 250 GETs (199 MB). With --num_workers 0: 125 GETs (99 MB). - patch 128^3, 125 patches, 64 distinct chunks: 1000 / 1000 / 241 / 64 GETs. The logits are byte-identical across all runs of a configuration. The counts match a chunk-grid simulation of the sliding window with round-robin worker dispatch. Main rejects the flag. test_inference_chunk_cache.py covers the parser default, main to Inferer, Inferer to VCDataset, the negative-value check, and a local VCDataset whose cache survives pickling (as for DataLoader workers) and reads identically. All 6 fail against main's source. The stub Inferer in test_inference_nnunet_normalization.py gains the new attribute. Reported by @aistae in ScrollPrize#1325, with counting method and simulation by @TAUIL-Abd-Elilah. The cache itself is @robertlangdonn's ScrollPrize#1373. ScrollPrize#1543 (@blackemberlabs) proposed a persistent on-disk cache for the same path. Refs ScrollPrize#1325
|
@CVasilopoulos is attempting to deploy a commit to the scroll Team on Vercel. A member of the Team first needs to authorize it. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
In one sentence:
vesuvius.predict --chunk_cache_mb 512turns on the in-memory LRU chunk cache that #1373 added toVolume, so overlapping patches reuse chunks already downloaded instead of fetching them again.One real example: Starting with the published Scroll 1 volume
https://dl.ash2txt.org/full-scrolls/Scroll1/PHercParis4.volpkg/volumes_zarr_standardized/54keV_7.91um_Scroll1A.zarr, I ranvesuvius.predicton a 192³ region with 192³ patches and 4 DataLoader workers. Without the flag it made 1000 chunk requests (796 MB). With--chunk_cache_mb 512it made 250 (199 MB). With--chunk_cache_mb 512 --num_workers 0it made 125, one per distinct chunk. The logits were byte-identical in all three runs.Before: #1373 made
open_zarr(cache=True)andVolume(cache=True, cache_size_mb=...)available, but nothing in the inference path passes them.VCDatasethas no cache argument, andvesuvius.predicthas no flag. Patches overlap by 50% by default and are not aligned to chunks, so every patch fetches all the chunks it touches again. That is the amplification @aistae measured in #1325 and @TAUIL-Abd-Elilah reproduced by counting HTTP requests. On main,--chunk_cache_mbiserror: unrecognized arguments.After this PR:
--chunk_cache_mb Npassescache=True, cache_size_mb=NfromInfererthroughVCDatasettoVolume. Each DataLoader worker keeps its own cache of up to N MB of stored chunk bytes, evicting least-recently-used chunks. The default is0, which leaves predict exactly as it is today: the same requests and the same output. Negative values are rejected. Like #1373, the cache needs zarr 3. With zarr 2,open_zarrraises its existingNotImplementedError.Proof: Upstream main
757f70c01compared with this branch on the same base, same volume, same--bbox 6400:6592,5120:5312,5120:5312(level 0, inside the scroll), overlap 0.5, batch 1,--disable_tta, CPU. Requests are counted, not timed. Asitecustomize.pyonPYTHONPATHwrapsfsspec'sHTTPFileSystem._cat_filein every process, including the DataLoader workers, and appends one line per request.chunk GETscounts successful requests for0/z/y/xkeys.logits sha256hashes the writtenlogits_part_0.zarrandcoordinates_part_0.zarrwith patches sorted by coordinate.Patch 192³ (the patch size of
hf://scrollprize/surface_recto), 64 patches over 125 distinct chunks:69eaa10571db94f669eaa10571db94f6--chunk_cache_mb 512(4 workers)69eaa10571db94f6--chunk_cache_mb 512 --num_workers 069eaa10571db94f6--chunk_cache_mb 512error: unrecognized arguments: --chunk_cache_mb 512, exit 2Patch 128³ (the configuration in #1325), 125 patches over 64 distinct chunks:
1f2f92447af1dfc41f2f92447af1dfc4--chunk_cache_mb 512(4 workers)1f2f92447af1dfc4--chunk_cache_mb 512 --num_workers 01f2f92447af1dfc4Excerpt of the terminal output (192³, main, then this PR with the flag):
All eight counts match a simulation of the sliding-window grid over the 128³ chunk grid, with PyTorch's round-robin dispatch of batches to workers and one LRU per worker. Main and this PR print the same counts for the same command.
Tests: the new
tests/models/run/test_inference_chunk_cache.pyhas 6 passed with this PR and 6 failed against main's source.tests/models/run+tests/data/test_chunk_cache.py+tests/data/test_vc_dataset_bbox.pygo from 73 passed, 1 skipped on main to 79 passed, 1 skipped.Why / where this is useful:
Anyone running
vesuvius.predictagainst a remote volume, for a--bboxregion or a whole scroll, pays for the same chunks several times over. As @TAUIL-Abd-Elilah noted in #1325, their link caps near 11 MB/s, so request count decides whether a run fits in a night. With this flag the requests for one region drop about 4 times with the default 4 workers. With--num_workers 0they drop to one per chunk, 8 to 16 times fewer here. It uses cache code that is already merged and tested. It needs no local disk and cannot serve stale data across runs.Details
What changed
vesuvius/src/vesuvius/models/run/inference.py:--chunk_cache_mbparser flag (int, default 0;--chunk-cache-mbalso works);Inferer(chunk_cache_mb=0)argument with a>= 0check;_create_dataset_and_loaderpassescache=chunk_cache_mb > 0, cache_size_mb=chunk_cache_mbtoVCDataset.vesuvius/src/vesuvius/data/vc_dataset.py:VCDataset(cache=False, cache_size_mb=256)is documented and forwarded toVolume, with the same names and defaults asVolume.vesuvius/docs/inference.md: one row in the argument table.vesuvius/tests/models/run/test_inference_chunk_cache.pycovers:Inferer;Infererforwardingcache/cache_size_mbfor 0 and 512;VCDatasetwith the cache whose store is aCacheStore, survivespickleas DataLoader workers receive it, reads the same patches as an uncached dataset, and records hits within its size bound.vesuvius/tests/models/run/test_inference_nnunet_normalization.py: the hand-built stubInferergainschunk_cache_mb = 0.Design notes
--num_workers 0reaches one request per chunk, at the cost of reading in the main process.N × max(1, num_workers)MB of stored chunk bytes. For this compressed volume a chunk is about 0.8 MB, so 512 MB holds about 640 chunks per worker.Tested
python:3.14-slimwith Python 3.14.7, zarr 3.3.0, fsspec 2026.7.0 and torch 2.14.0+cpu. Each run was a container capped at 12 GB and 8 CPUs. Not tested on GPU, macOS or Windows.https://dl.ash2txt.org/full-scrolls/Scroll1/PHercParis4.volpkg/volumes_zarr_standardized/54keV_7.91um_Scroll1A.zarr. Level 0, shape 14376×7888×8096, chunks 128³, blosc/zstd, about 0.8 MB per chunk.vesuvius.predict --model_path tiny_train_py.pth --input_dir <volume> --output_dir <out> --device cpu --disable_tta [--patch_size 192,192,192] --bbox 6400:6592,5120:5312,5120:5312 [--chunk_cache_mb 512] [--num_workers 0]train_pyUNet (4,837 parameters, seed 0), built withNetworkFromConfigas intest_inference_legacy_checkpoint.py. It only drives the read path, which does not depend on the model. Its logits are compared byte for byte and not interpreted. I did not usesurface_recto(819 MB), because 125 patches of it on CPU per run was not practical here.Limitations
--chunk_cache_dir, and the two can coexist.CacheStoredoes not cache misses. Absent chunks in sparse or masked volumes are still requested each time. In these runs, the 40 failed requests in every row are zarr v2 metadata probes (0/.zgroup,0/.zattrs,zarr.json, per-level.zattrs/.zgroup), not chunks.Prior work and credit
HTTPFileSystem._cat_file) and its DataLoader-worker caveat, and the simulation that the counts here match.CacheStoreat @bruniss's suggestion. This PR only exposes it in predict.simplecache, closed by its author in favour of vesuvius: fix zarr 3 Volume multiscale reads #1177) and vesuvius: persistent on-disk chunk cache for remote volumes #1543 (@blackemberlabs, persistent on-disk cache wired into predict, closed unreviewed by the time-limit bot on 2026-09-16) took the disk route. This PR copies no code from either. If vesuvius: persistent on-disk chunk cache for remote volumes #1543 is reopened, the two touch neighbouring lines ininference.pyandvc_dataset.py, and I am happy to rebase.vesuvius.predict.AI-assisted (Claude Code), human-directed.