Repository navigation
Conversation
…mize `optimize()` runs in the caller's process, but `DataProcessor.run` seeded `random`, `numpy` and `torch` at the top of `run` and never restored them, so the seed the caller chose was silently replaced. Everything random after an `optimize` call was the same whatever seed was set, and differed depending on whether an `optimize` had already run (only when a cache did not exist yet). Save the states before seeding and restore them in a `finally`, so the fixed seed only covers the processing work itself. The work moves to `_run` so the restore covers every exit path, including an exception. Fixes Lightning-AI#944
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #945 +/- ##
=====================================
Coverage 82% 82%
=====================================
Files 72 72
Lines 16445 16442 -3
=====================================
- Hits 13505 13503 -2
+ Misses 2940 2939 -1 🚀 New features to boost your workflow:
|
|
Thanks for the PR and the clear write-up! One gap: I think there's a simpler fix than restoring every generator: avoid changing global random state altogether.
|
…the global generators Expose random_seed on optimize() and map() and draw the per-worker shuffle from np.random.RandomState(random_seed), so litdata never touches the caller's global random / NumPy / torch (CPU or GPU) state. fn no longer inherits seed 42; seed it yourself if needed.
|
Thanks, that's a cleaner fix. Done in
I also added |
There was a problem hiding this comment.
🟡 Changes recommended
Weighted-shuffle RNG isolation lacks regression coverage, and the change leaves prepare_structure randomness uncontrolled by random_seed without documenting the altered determinism contract.
2 open issues
What changed in this PR
This PR addresses #944 by removing processing-time global RNG seeding and using a local NumPy generator for worker-item shuffling.
Changes:
- Replace global seeding with a local shuffle generator.
- Expose and document
random_seedonmap()andoptimize(). - Add regression tests for caller RNG preservation and avoidance of global seeders.
| File | Description |
|---|---|
tests/processing/test_data_processor.py |
Adds caller RNG-state and global-seeder regression tests. |
src/litdata/processing/functions.py |
Exposes and forwards the shuffle-only random_seed option. |
src/litdata/processing/data_processor.py |
Removes global seeding and passes a local generator to weighted shuffling. |
Review effort: Balanced
- fold the leftover _run helper back into run - avoid reassigning the typed rng argument in _map_items_to_workers_weighted - document that recipes must seed their own randomness - add weighted / file-size shuffle coverage for local RNG forwarding - silence S311 on the caller-state test
Co-authored-by: Deependu <deependujha21@gmail.com>

Summary
optimize()andmap()runDataProcessor.runin the caller's process, andrunseeded the globalrandom, NumPy and torch generators withrandom_seed(default42) and never restored them. That threw away the seed the caller had set before the call, so everything random afterwards (model init, shuffling, sampling) was the same whatever seed the caller picked. It also changed depending on whether anoptimizecall had happened earlier in the run, for example only when a cache didn't exist yet.litdata only needs randomness for one thing: shuffling the items assigned to each worker in the weighted / file-size mapping. That shuffle now uses a local
np.random.RandomState(random_seed), andrunno longer callsrandom.seed,np.random.seedortorch.manual_seed.Fixes #944
Changes
DataProcessor.run: dropped the three global seeding calls. Builds a localnp.random.RandomState(self.random_seed)and passes it to both_map_items_to_workers_weightedcall sites (explicitweightsand file-size reordering)._map_items_to_workers_weighted: new optionalrngargument.Nonekeeps the oldnp.randombehaviour for direct callers.optimize()/map(): exposerandom_seed: int | None = 42and forward it toDataProcessor.Nonegives a non-deterministic shuffle.random_seednow say it only seeds the worker shuffle.Behaviour notes
RandomState(42)produces the same sequence asnp.random.seed(42), and nothing between the old seeding point and the shuffle draws from the global NumPy generator. Sooptimize/mapassign and order items exactly as before.random_seedalso seeded the parent process, so a recipe'sprepare_structureand, understart_method="fork"(the default in notebooks), the forked workers'fninherited the seeded state. Under the defaultspawnstart method,fnnever inherited it. Code that relied on litdata seeding its randomness should seed insidefn/ the recipe.Tests
In
tests/processing/test_data_processor.py:test_optimize_preserves_the_callers_random_state[0|1]: seeds the caller, runsoptimize, and checks that the next draw fromrandom, NumPy and torch is the one the caller would have got without the call. On the unpatched tree both seeds returnrandom=0.639427, exactly as reported in the issue.test_optimize_does_not_seed_the_global_generators: fails ifrandom.seed,numpy.random.seedortorch.manual_seedis called duringoptimize. This also guards against torch queueing lazy CUDA / MPS / XPU seeds.test_weighted_shuffle_uses_local_rng[weights|file_sizes]: covers both weighted branches. Checks that the caller's NumPy state is preserved, that aRandomStateis forwarded, and that the shuffle is the same for the samerandom_seedwhatever the caller seeded. It fails ifrng=is dropped from either call site.tests/processing: 150 passed, 4 skipped.