Fix deepcopy bottleneck in state_dict() - #8387
Open
yuxin00j wants to merge 1 commit into
Open
Conversation
yuxin00j
force-pushed
the
fix-deepcopy-state-dict
branch
5 times, most recently
from
August 4, 2026 05:51
2ebcc1a to
ec9bb04
Compare
This commit implements explicit state_dict serialization across the IterableDataset iterators, replacing the previous fallback logic that relied on copy.deepcopy() of python generators, which raised Pickling errors.
yuxin00j
force-pushed
the
fix-deepcopy-state-dict
branch
from
August 4, 2026 05:53
ec9bb04 to
a28ff3e
Compare
deepcopy bottleneck in IterableDataset.state_dict() during shuffledeepcopy bottleneck in state_dict()
zhixiangli
reviewed
Aug 4, 2026
| if self._state_dict: | ||
| return deepcopy(self._state_dict) | ||
|
|
||
| def _fast_copy(state): |
There was a problem hiding this comment.
How about moving this func to module level for reusability?
| return [_fast_copy(v) for v in state] | ||
| elif hasattr(state, "copy"): | ||
| return state.copy() | ||
| return state |
There was a problem hiding this comment.
We need to throw TypeError for unexpected types, such as those outside of the expected dict, list, tuple, primitive, numpy scalar, and numpy array types.
| if not self._state_dict: | ||
| return {} | ||
| # Since ex_iterable.state_dict() returns a fresh isolated dict, we don't need a full recursive deepcopy. | ||
| return dict(self._state_dict) |
| for i in range(len(state)): | ||
| state[i] = _inner_load_state_dict(state[i], new_state[i]) | ||
| return state | ||
| return deepcopy(new_state) |
There was a problem hiding this comment.
Could you confirm are there any other deepcopy needed to be replaced with _fast_copy?
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.
Fix #8393
This PR significantly resolves a major CPU bottleneck in
IterableDatasetdataloading whenbuffer_sizeandmax_buffer_input_shardsare > 1.The Bug:
Currently,
_BaseExamplesIterable.state_dict()falls back to performing a full recursivecopy.deepcopy(self._state_dict).In nested iterator chains (like
BufferShuffledExamplesIterablewrappingCyclingMultiSourcesExamplesIterablewrapping multipleArrowExamplesIterableshards), thestate_dict()is queried and updated on every yielded row.Because
copy.deepcopyrelies on Python'spicklemechanics for object introspection and memoization, it incurs significant overhead. Worse,pickleoften crashes when it encounters unpicklable objects like generators. Whenmbisis scaled up and the dictionary size grows, the deepcopy completely blocks the CPU, hiding underneath the shuffle buffer processing.The Fix:
We replaced the expensive
deepcopy()logic with a dynamic, lightweight_fast_copy()helper directly inside_BaseExamplesIterable.state_dict().Because dataset
state_dicts strictly consist of standard collections and primitive state objects (and importantly, contain no circular references),_fast_copy()manually recursively walksdictandliststructures. For any other object, it natively returns the object (or uses its.copy()method if available), bypassingpickleanddeepcopyentirely.This allows us to dynamically serialize state without explicitly overriding
state_dictin every single dataset subclass, restoring maintainability and drastically improving speed.Impact:
We measured workloads using a high number of concurrent shards (
max_buffer_input_shards=10):This single PR shaves ~38 seconds of pure CPU overhead off the workload, generating a 36% overall speedup, entirely restoring the expected linear scaling of shuffle buffers when reading across many shards!