@@ -26,18 +26,14 @@ class DataLoaderStateCallback(Callback):
2626 def __init__ (
2727 self ,
2828 distributor_type : str | None = None ,
29- name : str = "" ,
3029 ) -> None :
3130 super ().__init__ ()
3231 self .distributor_type = distributor_type
33- self .name = name
3432 self .config : Any = None
3533 self .state : dict [int , NoReplaceShardlistState ] = {}
3634 self .verbose = True
3735
3836 def _update_state_from_batch (self , data_batch : dict [str , torch .Tensor ]) -> None :
39- if "sample_worker_id" not in data_batch :
40- return # batch has no position metadata (shuffle=False or iterable data_source)
4137 worker_ids = data_batch ["sample_worker_id" ].tolist () # [B]
4238 epochs = data_batch ["sample_epoch" ].tolist () # [B]
4339 indices = data_batch ["sample_index" ].tolist () # [B]
@@ -50,8 +46,6 @@ def _update_state_from_batch(self, data_batch: dict[str, torch.Tensor]) -> None:
5046 ):
5147 self .state [worker_id ] = NoReplaceShardlistState (epoch = epoch , index = index )
5248
53- _ACTIVE_DISTRIBUTOR_TYPES = ("no_replace" , "data_packer" )
54-
5549 def on_training_step_batch_end (
5650 self ,
5751 model : ImaginaireModel ,
@@ -60,7 +54,7 @@ def on_training_step_batch_end(
6054 loss : torch .Tensor ,
6155 iteration : int = 0 ,
6256 ) -> None :
63- if self .distributor_type in self . _ACTIVE_DISTRIBUTOR_TYPES :
57+ if self .distributor_type == "no_replace" :
6458 self ._update_state_from_batch (data_batch )
6559
6660 def on_training_step_end (
@@ -71,7 +65,7 @@ def on_training_step_end(
7165 loss : torch .Tensor ,
7266 iteration : int = 0 ,
7367 ) -> None :
74- if self .distributor_type in self . _ACTIVE_DISTRIBUTOR_TYPES :
68+ if self .distributor_type == "no_replace" :
7569 if self .verbose :
7670 if iteration % self .config .trainer .logging_iter == 0 :
7771 msg = "\n "
@@ -80,10 +74,10 @@ def on_training_step_end(
8074 log .info (msg )
8175
8276 def has_checkpoint_state (self ) -> bool :
83- return self .distributor_type in self . _ACTIVE_DISTRIBUTOR_TYPES
77+ return self .distributor_type == "no_replace"
8478
8579 def state_dict (self ) -> dict [int , dict [str , int ]]:
86- if self .distributor_type not in self . _ACTIVE_DISTRIBUTOR_TYPES :
80+ if self .distributor_type != "no_replace" :
8781 return {}
8882
8983 state_dict : dict [int , dict [str , int ]] = {}
@@ -96,122 +90,18 @@ def state_dict(self) -> dict[int, dict[str, int]]:
9690 return state_dict
9791
9892 def load_state_dict (self , state_dict : dict [int , dict [str , int ]]) -> None :
99- if self .distributor_type not in self . _ACTIVE_DISTRIBUTOR_TYPES :
93+ if self .distributor_type != "no_replace" :
10094 return
10195
10296 if not state_dict :
10397 log .info ("No dataloader state found in checkpoint" )
10498 return
10599
106100 self .state = {}
107- # Build env var prefix. For data_packer, namespacing avoids conflicts
108- # when multiple DataPackerDataLoader instances share the same process
109- # (e.g. inside JointDataPackerDataLoader). name="" → original format.
110- _dp_pfx = f"DP_STATE_{ self .name } _" if self .name else "DP_STATE_"
111101 for worker_id , per_worker_state in state_dict .items ():
112102 epoch = per_worker_state ["epoch" ]
113103 index = per_worker_state ["index" ]
114104 self .state [worker_id ] = NoReplaceShardlistState (epoch = epoch , index = index )
115- if self .distributor_type == "data_packer" :
116- os .environ [f"{ _dp_pfx } WORKER_{ worker_id } _EPOCH" ] = str (epoch )
117- os .environ [f"{ _dp_pfx } WORKER_{ worker_id } _INDEX" ] = str (index )
118- log .info (f"Loaded data_packer dataloader state for worker { worker_id } : epoch={ epoch } , index={ index } " )
119- else :
120- os .environ [f"NSL_STATE_WORKER_{ worker_id } _EPOCH" ] = str (epoch )
121- os .environ [f"NSL_STATE_WORKER_{ worker_id } _INDEX" ] = str (index )
122- log .info (f"Loaded no_replace dataloader state for worker { worker_id } : epoch={ epoch } , index={ index } " )
123-
124-
125- class JointDataLoaderStateCallback (Callback ):
126- """Checkpoint/resume state for ``JointDataPackerDataLoader``.
127-
128- Manages two levels of state in a single DCP checkpoint entry
129- (``checkpoint_component = "dataloader"``):
130-
131- 1. **Outer** ``global_id`` — the number of batches the outer loader has
132- yielded. Restored via ``outer_loader.set_start_iteration(global_id)``
133- so the deterministic dataset-selection sequence resumes from the correct
134- step.
135-
136- 2. **Inner** per-dataset, per-worker ``(epoch, index)`` — one
137- ``DataLoaderStateCallback`` per inner loader, keyed by the dataset name.
138- Each inner callback sets namespaced env vars on ``load_state_dict`` so
139- workers fast-forward to the saved sample position.
140-
141- Usage in experiment configs::
142-
143- joint_loader = JointDataPackerDataLoader(dataloaders={...}, seed=42)
144- exp["dataloader_train"] = joint_loader
145- exp["trainer"]["callbacks"]["dataloader_state"] = JointDataLoaderStateCallback(
146- outer_loader=joint_loader,
147- distributor_type="data_packer",
148- )
149-
150- The ``checkpoint_component = "dataloader"`` class attribute ensures the DCP
151- checkpointer's ``_DataloaderWrapper`` discovers exactly this callback (it
152- picks the first matching callback). Do **not** also register standalone
153- ``DataLoaderStateCallback`` instances for the inner loaders — this class
154- already handles them all.
155- """
156-
157- checkpoint_component : str = "dataloader"
158-
159- def __init__ (
160- self ,
161- outer_loader : Any ,
162- distributor_type : str = "data_packer" ,
163- ) -> None :
164- super ().__init__ ()
165- self ._outer = outer_loader
166- self ._inner : dict [str , DataLoaderStateCallback ] = {
167- name : DataLoaderStateCallback (distributor_type = distributor_type , name = name )
168- for name in outer_loader ._names
169- }
170- self .config : Any = None
171-
172- def _update_state_from_batch (self , batch : dict ) -> None :
173- name = batch .get ("dataset_name" )
174- if name in self ._inner :
175- self ._inner [name ]._update_state_from_batch (batch )
176-
177- def on_training_step_batch_end (
178- self ,
179- model : Any ,
180- data_batch : dict ,
181- output_batch : dict ,
182- loss : Any ,
183- iteration : int = 0 ,
184- ) -> None :
185- self ._update_state_from_batch (data_batch )
186-
187- def on_training_step_end (
188- self ,
189- model : Any ,
190- data_batch : dict ,
191- output_batch : dict ,
192- loss : Any ,
193- iteration : int = 0 ,
194- ) -> None :
195- if self .config and iteration % self .config .trainer .logging_iter == 0 :
196- msg = f"\n JointDataPackerDataLoader global_id={ self ._outer ._global_id } \n "
197- for name , cb in self ._inner .items ():
198- for wid , state in cb .state .items ():
199- msg += f" [{ name } ] worker { wid } : epoch={ state .epoch } , index={ state .index } \n "
200- log .info (msg )
201-
202- def has_checkpoint_state (self ) -> bool :
203- return True
204-
205- def state_dict (self ) -> dict :
206- return {
207- "global_id" : self ._outer ._global_id ,
208- ** {name : cb .state_dict () for name , cb in self ._inner .items ()},
209- }
210-
211- def load_state_dict (self , state : dict ) -> None :
212- global_id = state .get ("global_id" , 0 )
213- self ._outer .set_start_iteration (global_id )
214- log .info (f"JointDataLoaderStateCallback: resumed outer global_id={ global_id } " )
215- for name , cb in self ._inner .items ():
216- if name in state :
217- cb .load_state_dict (state [name ])
105+ os .environ [f"NSL_STATE_WORKER_{ worker_id } _EPOCH" ] = str (epoch )
106+ os .environ [f"NSL_STATE_WORKER_{ worker_id } _INDEX" ] = str (index )
107+ log .info (f"Loaded no replace dataloader state for worker { worker_id } : epoch={ epoch } , index={ index } " )
0 commit comments