Skip to content

perf(raw): StreamingRawDataset cloud download throughput - #863

Merged
tchaton merged 48 commits into
mainfrom
feature/raw-streaming-perf
Jul 28, 2026
Merged

perf(raw): StreamingRawDataset cloud download throughput#863
tchaton merged 48 commits into
mainfrom
feature/raw-streaming-perf

Conversation

@tchaton

@tchaton tchaton commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR hardens StreamingRawDataset cloud downloads for correctness first, then improves default concurrency.

  • Correctness: dedicated LoopRunner (optional uvloop), fork/spawn-safe clients, atomic cache + index publishes, and batch-level hang protection.
  • Throughput is regime-dependent: clearest wins at low worker counts; at high workers (w=24) Stage 1 is best framed as robustness, not a single marketing Δ%.
  • Stage 1: max_concurrent_downloads=None picks a size-aware aggregate budget (bandwidth vs Little’s-law, clamped), instead of a fixed 64 permits/worker stampede.

HEAD of this branch: 3b0bdcc.

What's in this PR

  • LoopRunner with optional uvloop (litdata[extras]); fork/spawn-safe runtime clients
  • Atomic cache publishes, including index tmp + os.replace
  • Batch-level download_timeout (default 120): one asyncio.wait_for around _download_batch; hang-recovery cancels poisoned _inflight so retries can proceed
  • Fast path preserved: per-item GETs stay bare when hedge_delay=0 (default); size-gated request hedging stays off by default
  • Stage 0 bench protocol: max(≥300 batches, ≥30s), repeats/interleave, SHA/ts artifacts; harness records before_sha / after_sha from each PYTHONPATH tree
  • Stage 1 adaptive concurrency (max_concurrent_downloads=None):
    • size-aware budget = max(bandwidth, Little’s-law) then clamp [32, 512], split across workers (floor 8)
    • Little’s-law arm size-gated (sub-MiB medians only) so 1/10/100 MiB objects stay bandwidth-bounded
    • single-process path capped at 128
    • explicit int → exact permit count (no silent clamp)
  • Parallel ranged GETs remain opt-in (range_parallel_threshold=0)

Benchmarks

Setup: ImageNet val raw (50k JPEGs) via S3 · batch_size=64 · spawn · persistent_workers=True · 48 vCPU · Python 3.12.11 · range_parallel_threshold=0, hedge_delay=0.

Protocol: warm max(1, workers×prefetch_factor), then time until both ≥300 batches and ≥30s. Prefer medians + spreads.

Confirm cell (authoritative for Stage 1 @ w=24)

Provenance-verified interleaved A/B, w=24 p=0, n=3.

SHA median ips spread runs
before 52dba61 (post batch-timeout, pre Stage 1; fixed max_concurrent_downloads=64) 3816 30% 2705 / 3816 / 3851
after ba9da13 (Stage 1 adaptive) 6049 21% 4863 / 6049 / 6136

Δ median ≈ +59%. Verdict (a): before ≈3.7k matches the Stage 1 grid before band — robustness / session drift on fixed-64, not wrong-tree. Do not treat prior full-grid +53% as a headline without this SHA proof.

Artifacts: benchmarks/results/raw_before_vs_after.ba9da13.1785268543.json, benchmarks/results/stage1_confirm_w24_ba9da13.log.

Stage 1 full-grid medians (n=5)

Runner 46f2d45. Before = fixed 64; after = Stage 1 adaptive. Artifact: benchmarks/results/raw_before_vs_after.46f2d45.1785267013.json.

Provenance note: this grid predates tree-SHA recording in meta (before_sha / after_sha null). Treat mid-w Δ ≲20% as within run-to-run noise. The confirm cell above is the SHA-verified claim for w=24.

w p before after Δ%
2 0 1053 1224 +16%
2 16 1146 1154 +1%
4 0 2029 2075 +2%
4 16 2215 2160 −2%
8 0 4592 3545 −23%
8 16 4142 4044 −2%
16 0 3637 5269 +45%
16 16 3848 5336 +39%
24 0 3746 5742 +53%
24 16 3595 5619 +56%

Spreads are often large (tens of percent). The w=8 p=0 −23% cell looks like A/A noise (both sides ~60–64 permits) and was not used to gate the clamp at w≥16. Prefer the confirm cell for Stage 1 @ w=24 framing.

Historical dig (batch-timeout story, stale)

Single-run w=24 p=0 cells that motivated the timeout fix — not current HEAD claims:

step ips label
Old main baseline 6927 single-run before (5d8cfc1 in merge) — raw_before_vs_after.before.json
After, per-item timeout (broken) 4404 after @ b991c7draw_before_vs_after.json
download_timeout=0 fast path 6559 @ 6ab527draw_decisive_timeout0.6ab527d.1785252483.json
Batch-timeout confirm (download_timeout=120) 6697 @ 6ab527draw_confirm_batch_timeout.6ab527d.1785252695.json

Takeaway from the dig: post-timeout, w=24 with 64 permits was back near the old main band; the “must hard-clamp because of stampede” premise was weak. Stage 1 remains valuable as a size-aware default, verified at high-w by the confirm cell.

Design notes: benchmarks/ADAPTIVE_CONCURRENCY.md.

API notes

setting behavior
max_concurrent_downloads=None (default after this PR) adaptive size-aware budget
max_concurrent_downloads=<int> exactly that many permits — no silent clamp
download_timeout=120 batch-level hang protection (0 disables)
hedge_delay=0 hedging off; per-item GETs on the fast path
range_parallel_threshold=0 ranged parallel GETs opt-in

User-facing README stays high-level; deep formula lives in the design note above.

Test plan

  • pytest tests/raw/ -v (113 passed @ ba9da13)
  • pytest tests/streaming/test_client.py tests/streaming/test_downloader.py -v (if touched)
  • Stage 0 bench protocol + Stage 1 formula (sentinel, size-gated latency, single-process cap)
  • Confirm cell w=24 p=0 n=3 with proven before_sha / after_sha
  • LRU hit-rate spot-check at w=8 p16 (LITDATA_RAW_DEBUG=1) → ~3%

Follow-ups

  1. Downloader conformance suite (+ throttle-retry counts) — blocks Stage 3
  2. Prefetch floor / Stage 2 hit-rate controller
  3. AIMD Stage 3 — after downloader contract

Made with Cursor

Dedicated LoopRunner/uvloop, look-ahead prefetch, size-gated hedging,
atomic cache, fork-safe clients, and opt-in ranged GETs lift ImageNet-val
raw throughput to ~7350 samples/s (w=24, prefetch=16); document knobs and
sweep results.

Co-authored-by: Cursor <cursoragent@cursor.com>
@tchaton
tchaton requested a review from justusschock as a code owner July 28, 2026 13:08
@codecov-commenter

codecov-commenter commented Jul 28, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 80.09950% with 200 lines in your changes missing coverage. Please review.
✅ Project coverage is 81%. Comparing base (5d8cfc1) to head (267a5af).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@         Coverage Diff          @@
##           main   #863    +/-   ##
====================================
  Coverage    81%    81%            
====================================
  Files        56     56            
  Lines      8374   9303   +929     
====================================
+ Hits       6795   7566   +771     
- Misses     1579   1737   +158     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tchaton and others added 26 commits July 28, 2026 13:31
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Break compound exit-status asserts and use contextlib.suppress for
expected OSError/BrokenBarrierError paths so pre-commit.ci passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Publish A/B numbers for StreamingRawDataset (stock main vs LoopRunner/prefetch)
in README, skill docs, and benchmark JSON for PR #863.

Co-authored-by: Cursor <cursoragent@cursor.com>
Default hedge_delay=0 and skip hedge/timeout wrappers when both are off so
prefetch=0 no longer pays for disabled safety features. Move optional uvloop
to extras, lengthen the A/B harness warm/timed windows, and replace the
short-window w=24 artifact (10556 / conflicting 5361 vs 7350) with a
long-window remeasure (~6814 before, ~6635/6756 after).

Co-authored-by: Cursor <cursoragent@cursor.com>
Capture practical chunk_bytes guidance and ordered-source shuffle caveats
from expert discussion so README and agent skills stay aligned.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add agent-facing FUSE warning, shuffle-before-optimize tip, and order-of-magnitude ImageNet ballpark to litdata skills and the README FAQ.

Co-authored-by: Cursor <cursoragent@cursor.com>
…atrix

Enable look-ahead by default (~2× typical batch) and replace the w=24-only
snippet with a full workers×prefetch Before vs After table from the long-window harness.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace try/except/pass with contextlib.suppress for SIM105/S110. Clarify
that max_prefetch is per DataLoader worker and aggregate look-ahead scales
with num_workers × max_prefetch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cap per-worker look-ahead to a ~64-item aggregate so default max_prefetch=16
stays strong at low workers without overscheduling at w≥8; document after-p0
vs main at high workers and reframe PR value around correctness.

Co-authored-by: Cursor <cursoragent@cursor.com>
Per-item wait_for under default download_timeout=120 was the w=24
regression; hang protection now wraps the batch gather once so the
fast path coexists with defaults. Index cache writes use tmp+replace.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cancelling _resolve_index wrappers alone left poisoned prefetch downloads
in _inflight, so every retry paid the full budget. Also catch both
TimeoutError aliases, avoid rewriting item timeouts when budget is None,
and floor the batch budget by aggregate transfer time.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Lead README/PR presentation with regime summary and post-fix w=24
noise band; stop overwriting confirm/decisive/highw result JSON in place.
Restore the 6697 confirm run under a SHA/ts-suffixed artifact.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep lru/ranged/worker-sweep artifacts append-only so re-runs cannot
overwrite prior dig results in place.

Co-authored-by: Cursor <cursoragent@cursor.com>
tchaton and others added 20 commits July 28, 2026 16:22
Clamp per-worker download permits from a size-aware aggregate budget so
high num_workers cannot open N×64 in-flight GETs. Fix A/B timing to
max(batches, seconds), add repeats/interleave with median+spread, and
document deferred adaptive stages.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reword "statics" to avoid codespell false positive, use a ternary for
median, and noqa the trusted local subprocess interleave invocation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Budget is max(bandwidth, latency) clamped to [32, 512] so ImageNet-sized
objects keep healthy mid-w aggregate. None defaults to adaptive; explicit
int is exact permits. Cache permits per pid; document honest Stage 1 premise.

Co-authored-by: Cursor <cursoragent@cursor.com>
Allow before trees that already have max_prefetch/LoopRunner as a fixed-64
baseline, pair same-(w,prefetch) in merge, and add --after-prefetch.

Co-authored-by: Cursor <cursoragent@cursor.com>
High-throughput cells can exhaust ImageNet val (~782 batches) before the
30s floor; restart the iterator so max(batches, seconds) can finish.

Co-authored-by: Cursor <cursoragent@cursor.com>
A/B showed w=8 p0 −23% under always-on clamp while w=16/24 gained
+40–55%. Below the gate keep historical 64 permits; high-w keep the
size-aware split. Track append-only A/B resume helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
Treat w8 −23% as likely A/A noise after budget widening. Latency model
only for sub-MiB medians; single-process adaptive capped at 128. Record
before_sha/after_sha from each PYTHONPATH tree; warn once on index PutObject.

Co-authored-by: Cursor <cursoragent@cursor.com>
Provenance-verified before_sha=52dba61 after_sha=ba9da13; before ~3.8k
confirms robustness framing, not wrong-tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
urlparse('C:\\...') yields scheme='c', which was rejected as an unsupported
cloud scheme and broke StreamingRawDataset on Windows CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sync the litdata skill with StreamingRawDataset adaptive concurrency,
batch-level timeouts, fork/atomic invariants, and bench trust hierarchy
so agents use and extend the raw path correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
…esolution

Exhaustive agent references for optimize/map I/O (FsProvider worker pools vs
Downloader ABC, FUSE→cloud resolve) and multi-node Studio jobs (sharding,
index merge, checkpoints, pitfalls).

Co-authored-by: Cursor <cursoragent@cursor.com>
Skip broadcast_object for input/output dirs by default so multi-node ranks
keep locally resolved paths. Auto-enable when paths contain a `{%strftime}`
template so ranks still share one expanded timestamp; allow broadcast_paths=True
to force alignment.

Co-authored-by: Cursor <cursoragent@cursor.com>
Broadcast-path tests used /tmp strings that trip ruff S108; swap to
non-tmp placeholders. Empty unused intersphinx_mapping so -W linkcheck
no longer fails when docs.python.org resets inventory fetches.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align the skip with the existing reason: nested spawn workers under
pytest-xdist can leave DataProcessor.join() stuck on darwin, burning CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@tchaton
tchaton merged commit 506ad6c into main Jul 28, 2026
35 checks passed
@tchaton
tchaton deleted the feature/raw-streaming-perf branch July 28, 2026 21:19
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.

2 participants