Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Share RAM image caches across DataLoader workers - #26086

Draft
glenn-jocher wants to merge 1 commit into
mainfrom
fix/ram-cache-worker-sharing
Draft

Share RAM image caches across DataLoader workers#26086
glenn-jocher wants to merge 1 commit into
mainfrom
fix/ram-cache-worker-sharing

Conversation

@glenn-jocher

@glenn-jocher glenn-jocher commented Sep 7, 2026

Copy link
Copy Markdown
Member

Scope

Extract only the RAM image-cache change from #26085 so it can be evaluated independently. This PR does not change DataLoader multiprocessing defaults, pinning, worker lifecycle, or model behavior. It does not fix the fork deadlock investigated in #26085.

The existing contiguous NumPy cache benefits from fork copy-on-write, but spawn/forkserver can serialize the full pixel buffer into each worker. Replace that buffer at the existing BaseDataset._ImageCache owner with multiprocessing.RawArray, shared by detection-family and classification datasets. Return private per-image copies before transforms, and reuse the image already retrieved in load_image to avoid copying twice.

Related history and boundaries

  • #9824: classification RAM growth reported in April 2024. Earlier attempts include closed/unmerged #9500 and #19947. The original growth mechanism is now closed as fixed, not an unresolved issue this PR should auto-close.
  • Merged #24670 and #24673 introduced preloading and the existing contiguous cache owner. This PR builds on that implementation; it does not replace it with a second cache abstraction.
  • Closed/unmerged #24364 explored explicit shared-memory tensors and constrained shared-memory handling. Its review requested worker-memory and constrained-storage regression evidence. Those concerns remain relevant here.
  • pytorch/pytorch#13246, open since 2018, discusses parent-memory replication in DataLoader workers. Its editor note explicitly distinguishes the NumPy workaround as fork-only, linking the discussion of start methods. That is the relevant remaining gap here. This PR addresses the image buffer, not arbitrary Python labels/metadata or all causes discussed there.
  • #22307 concerns independent DDP ranks each constructing their own cache. This PR does not share caches between independent GPU/rank processes. It shares one dataset's buffer with that process's DataLoader workers.
  • The inherited native-lock hang and pytorch/pytorch#130610 belong to Avoid fork-inherited image decoder deadlocks in Linux data loaders #26085, not this caching change.

The linked reports establish longstanding memory/reliability problems. No verified security advisory or CVE is asserted here.

Validation

Prior isolated tests of these exact cache-file changes, carried over from #26085:

  • Real 600-image EgoHands cache: 414,720,000 bytes. On Linux Python 3.12/PyTorch 2.11 and Python 3.14/PyTorch 2.14, eight workers under each of fork, spawn, and forkserver verified the same backing inode and content hash. Aggregate buffer PSS across the parent plus eight workers was 414,710,784 bytes, with zero private-dirty bytes. This measures the pixel buffer only, not total process memory.
  • The real eight-worker validation loader completed 38 batches on both Linux versions.
  • Real-image detection and classification augmentation checks left the complete cache hash unchanged.
  • macOS Python 3.14/PyTorch 2.9: eight spawn workers verified shared storage and private returned images.

Fresh checks on this cache-only branch: eight-worker macOS spawn sharing check, three existing dataloader tests, changed-code lint and diff whitespace checks.

Draft blockers

This is a separation for focused review, not a claim of LGTM. The previous Fable 5.1/high review identified concerns still applicable to this extracted implementation:

  1. RawArray can fall back from /dev/shm to temporary disk backing, and macOS uses temporary disk backing. The existing RAM budget check does not account for this storage requirement. Constrained storage and allocation failure need a safe design and validation before this is ready.
  2. Per-image copying adds work; representative cache-only throughput and memory measurements are still needed against the existing implementation.
  3. Sharing under worker startup does not make the cache generally pickleable/deep-copyable outside that startup context.

The global Linux forkserver compatibility objections from #26085 do not apply to this diff because it leaves the start method unchanged. A fresh independent Fable 5.1/high review remains required once the cache implementation is ready.

@UltralyticsAssistant UltralyticsAssistant added enhancement New feature or request python Pull requests that update python code WIP Work in progress labels Sep 7, 2026
@UltralyticsAssistant

Copy link
Copy Markdown
Member

👋 Hello @glenn-jocher, thank you for submitting a ultralytics/ultralytics 🚀 PR! This automated message confirms your contribution was received, and an Ultralytics engineer will assist with the review. To ensure a seamless integration of your work, please review the following checklist:

  • Define a Purpose: Clearly explain the purpose of your fix or feature in your PR description, and link to any relevant issues. Ensure your commit messages are clear, concise, and adhere to the project's conventions.
  • Synchronize with Source: Confirm your PR is synchronized with the ultralytics/ultralytics main branch. If it's behind, update it by clicking the 'Update branch' button or by running git pull and git merge main locally.
  • Ensure CI Checks Pass: Verify all Ultralytics Continuous Integration (CI) checks are passing. If any checks fail, please address the issues.
  • Update Documentation: Update the relevant documentation for any new or modified features.
  • Add Tests: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
  • Sign the CLA: Please ensure you have signed our Contributor License Agreement if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
  • Minimize Changes: Limit your changes to the minimum necessary for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." — Bruce Lee

For more guidance, please refer to our Contributing Guide. Don't hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀

@UltralyticsAssistant UltralyticsAssistant 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.

🔍 PR Review

Made with ❤️ by Ultralytics Actions

Reviewed the shared cache owner, detection load_image path, and classification integration. The private-copy semantics and reuse of the loaded image are coherent, but the new shared-buffer allocation introduces an unhandled constrained-storage failure path. Not LGTM until allocation is validated or fails safely.

💬 Posted 1 inline comment
  • 💡 MEDIUM ultralytics/data/base.py:81 RawArray can use a limited shared-memory or temporary-file backing store, but the existing preflight only checks psutil.virtual_memory().available (and ClassificationDataset does not preflight at all). On macOS or Linux with constrained /dev/shm/temporary storage, cache='ram' can pass validation and then fail here with an allocation error or exhaust the backing filesystem during dataset construction. Add an owner-level resource check with a safe uncached fallback, covering both detect…

Comment thread ultralytics/data/base.py
self.dtypes = np.array([im.dtype.str for im in images])
self.offsets = np.concatenate(([0], np.cumsum([im.nbytes for im in images])))
self.buffer = np.empty(self.offsets[-1], dtype=np.uint8)
self.buffer = RawArray("B", int(self.offsets[-1]))

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.

💡 MEDIUM: RawArray can use a limited shared-memory or temporary-file backing store, but the existing preflight only checks psutil.virtual_memory().available (and ClassificationDataset does not preflight at all). On macOS or Linux with constrained /dev/shm/temporary storage, cache='ram' can pass validation and then fail here with an allocation error or exhaust the backing filesystem during dataset construction. Add an owner-level resource check with a safe uncached fallback, covering both detection and classification paths.

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@glenn-jocher

Copy link
Copy Markdown
Member Author

Follow-up design review and handoff

Reviewed head: 0e0b968da243953238d6fd48ef743f6c6d372522. Keep this PR draft; the current RawArray implementation is not ready for LGTM. No implementation changes are made by this comment.

Better candidate to investigate

Replace the existing packed NumPy pixel buffer with a packed CPU Torch uint8 tensor, and use NumPy views when retrieving images. Investigate relying on PyTorch multiprocessing storage reducers to establish sharing only when a dataset is serialized for spawn/forkserver workers, rather than allocating shared backing unconditionally.

The intended benefit is to preserve ordinary RAM/copy-on-write behavior for zero-worker and fork loaders while avoiding a full pixel-cache copy per spawned worker. This is a proposal, not a validated drop-in fix. Verify the reducer behavior and lifetime on supported platforms and the Python/PyTorch support floor before relying on it. Independent distributed-training ranks are outside this sharing scope.

Blockers and design constraints

  • RawArray introduces a backing-storage requirement even for paths that previously needed only RAM. A real decoded repository image (2,624,400 bytes) successfully allocated in NumPy under an OS file-size limit of 65,536 bytes, while the proposed RawArray cache raised OSError 27. Shared backing can also fail through mechanisms that a Python OSError handler does not cover.
  • Torch shared storage still has shared-memory/platform capacity limits, including constrained container configurations, and may temporarily require both the original and shared buffer. Reusing a standard reducer does not solve capacity by itself.
  • Do not treat catching OSError and silently retaining an unshared image list as a complete fix: it restores per-worker duplication and does not cover SIGBUS-style failures.
  • The new per-image copy needs justification and measurement. Standard inspected augmentation paths already allocate before in-place changes, so cache corruption in those paths has not been demonstrated. However, true shared storage makes accidental writes visible across workers. Establish the intended mutation contract and validate it before retaining or removing the copy.

Next steps / acceptance evidence

  1. Prototype at the existing _ImageCache owner, keeping the diff minimal and leaving worker start-method defaults unchanged. Avoid parallel cache implementations or speculative platform scaffolding.
  2. Validate real detection and classification loaders on Linux, macOS, and Windows, with fork where supported, spawn/forkserver, zero workers, and eight workers. Include the supported version floor, repeated loader resets, and worker/storage cleanup.
  3. Prove that fork/zero-worker paths do not acquire a new shared-backing requirement, and that spawned workers share one pixel buffer. Measure backing identity/PSS or platform equivalents and parent peak memory; RSS alone cannot distinguish shared mappings from duplication.
  4. Exercise constrained shared memory/backing storage in disposable processes. Resolve the capacity behavior before calling this a compatibility-preserving replacement; a hard crash or unexplained memory multiplication is not acceptable.
  5. Verify cache contents through real augmentation paths and measure full-loader/training throughput and peak memory against the current implementation. Use a representative cache, not only a tiny smoke dataset.
  6. Resume the same reviewer on the implementation delta, then obtain a cold full-diff review of the final live head. If the candidate cannot meet these constraints, retaining the current cache and documenting spawn replication is preferable to claiming an incomplete universal fix.

Related context: PyTorch #13246 describes longstanding worker memory replication; Ultralytics #9824 and merged #24670/#24673 cover earlier cache improvements, while #24364 explored shared tensor storage. Recheck their exact scope rather than assuming they prove this proposal. These are memory/reliability concerns; no verified security advisory is established here.

Claude Code Fable 5.1 (high) recommends investigating the Torch-storage approach, but it has not been implemented or validated and is not yet LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request python Pull requests that update python code WIP Work in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants